Consolidate conjugation i18n strings (#11660)

This commit is contained in:
Michael Telatynski 2023-09-25 12:18:15 +01:00 committed by GitHub
parent f841757906
commit 0f59298f30
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": { "compilerOptions": {
"target": "es2016", "target": "es2016",
"jsx": "react", "jsx": "react",
"lib": ["es2020", "dom", "dom.iterable"], "lib": ["es2021", "dom", "dom.iterable"],
"types": ["cypress", "cypress-axe", "@percy/cypress", "@testing-library/cypress"], "types": ["cypress", "cypress-axe", "@percy/cypress", "@testing-library/cypress"],
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,

View file

@ -49,6 +49,7 @@ import { SettingLevel } from "./settings/SettingLevel";
import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController"; import MatrixClientBackedController from "./settings/controllers/MatrixClientBackedController";
import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import PlatformPeg from "./PlatformPeg"; import PlatformPeg from "./PlatformPeg";
import { formatList } from "./utils/FormattingUtils";
export interface IMatrixClientCreds { export interface IMatrixClientCreds {
homeserverUrl: string; homeserverUrl: string;
@ -356,15 +357,9 @@ class MatrixClientPegClass implements IMatrixClientPeg {
if (name) return name; if (name) return name;
if (names.length === 2 && count === 2) { if (names.length === 2 && count === 2) {
return _t("user1_and_user2", { return formatList(names);
user1: names[0],
user2: names[1],
});
} }
return _t("user_and_n_others", { return formatList(names, 1);
user: names[0],
count: count - 1,
});
} }
private inviteeNamesToRoomName(names: string[], count: number): string { 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 { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils"; import { formatList } from "../../../utils/FormattingUtils";
import { isValid3pidInvite } from "../../../RoomInvite"; import { isValid3pidInvite } from "../../../RoomInvite";
import GenericEventListSummary from "./GenericEventListSummary"; import GenericEventListSummary from "./GenericEventListSummary";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; 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); 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 }); return _t("timeline|summary|format", { nameList, transitionList: desc });
}); });
@ -150,7 +150,7 @@ export default class EventListSummary extends React.Component<
* included before "and [n] others". * included before "and [n] others".
*/ */
private renderNameList(users: string[]): string { 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 { _t } from "../../../languageHandler";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils"; import { formatList } from "../../../utils/FormattingUtils";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import { GetRelationsForEvent } from "../rooms/EventTile"; import { GetRelationsForEvent } from "../rooms/EventTile";
@ -100,7 +100,7 @@ export function findTopAnswer(pollEvent: MatrixEvent, voteRelations: Relations):
const bestAnswerTexts = bestAnswerIds.map(findAnswerText); const bestAnswerTexts = bestAnswerIds.map(findAnswerText);
return formatCommaSeparatedList(bestAnswerTexts, 3); return formatList(bestAnswerTexts, 3);
} }
export function isPollEnded(pollEvent: MatrixEvent, matrixClient: MatrixClient): boolean { 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 { mediaFromMxc } from "../../../customisations/Media";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils"; import { formatList } from "../../../utils/FormattingUtils";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip"; import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
@ -123,7 +123,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
undefined; undefined;
} }
const reactors = formatCommaSeparatedList(senders, 6); const reactors = formatList(senders, 6);
if (content) { if (content) {
label = _t("timeline|reactions|label", { label = _t("timeline|reactions|label", {
reactors, reactors,

View file

@ -19,7 +19,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { unicodeToShortcode } from "../../../HtmlUtils"; import { unicodeToShortcode } from "../../../HtmlUtils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils"; import { formatList } from "../../../utils/FormattingUtils";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow"; import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
@ -66,7 +66,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
}, },
{ {
reactors: () => { reactors: () => {
return <div className="mx_Tooltip_title">{formatCommaSeparatedList(senders, 6)}</div>; return <div className="mx_Tooltip_title">{formatList(senders, 6)}</div>;
}, },
reactedWith: (sub) => { reactedWith: (sub) => {
if (!shortName) { if (!shortName) {

View file

@ -28,6 +28,7 @@ import ErrorDialog from "../dialogs/ErrorDialog";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog"; import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import Heading from "../typography/Heading"; import Heading from "../typography/Heading";
import { formatList } from "../../../utils/FormattingUtils";
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => { export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
@ -82,58 +83,46 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
{_t("action|view")} {_t("action|view")}
</AccessibleButton> </AccessibleButton>
); );
let names: string = knockMembers let names = formatList(
.slice(0, 2) knockMembers.map((knockMember) => knockMember.name),
.map((knockMember) => knockMember.name) 3,
.join(", "); true,
);
let link: ReactNode = null; let link: ReactNode = null;
switch (knockMembersCount) { if (knockMembersCount === 1) {
case 1: { buttons = (
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 && (
<AccessibleButton <AccessibleButton
className="mx_RoomKnocksBar_link" className="mx_RoomKnocksBar_action"
element="a" disabled={!canKick || disabled}
kind="link_inline" kind="icon_primary_outline"
onClick={handleOpenRoomSettings} onClick={() => handleDeny(knockMembers[0].userId)}
title={_t("action|deny")}
> >
{_t("action|view_message")} <XIcon width={18} height={18} />
</AccessibleButton> </AccessibleButton>
); <AccessibleButton
break; className="mx_RoomKnocksBar_action"
} disabled={!canInvite || disabled}
case 2: { kind="icon_primary"
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name }); onClick={() => handleApprove(knockMembers[0].userId)}
break; title={_t("action|approve")}
} >
case 3: { <CheckIcon width={18} height={18} />
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name }); </AccessibleButton>
break; </>
} );
default: names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 }); 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 ( return (

View file

@ -95,9 +95,9 @@
"Delete widget": "Изтрий приспособлението", "Delete widget": "Изтрий приспособлението",
"Create new room": "Създай нова стая", "Create new room": "Създай нова стая",
"Home": "Начална страница", "Home": "Начална страница",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s и %(count)s други", "other": "<Items/> и %(count)s други",
"one": "%(items)s и още един" "one": "<Items/> и още един"
}, },
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
"collapse": "свий", "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?", "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", "Delete widget": "Suprimeix el giny",
"Home": "Inici", "Home": "Inici",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s i %(count)s altres", "other": "<Items/> i %(count)s altres",
"one": "%(items)s i un altre" "one": "<Items/> i un altre"
}, },
"%(items)s and %(lastItem)s": "%(items)s i %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s i %(lastItem)s",
"collapse": "col·lapsa", "collapse": "col·lapsa",

View file

@ -132,9 +132,9 @@
"Invalid file%(extra)s": "Neplatný soubor%(extra)s", "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?", "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!", "Something went wrong!": "Něco se nepodařilo!",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s a %(count)s další", "other": "<Items/> a %(count)s další",
"one": "%(items)s a jeden další" "one": "<Items/> a jeden další"
}, },
"%(items)s and %(lastItem)s": "%(items)s a také %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s a také %(lastItem)s",
"And %(count)s more...": { "And %(count)s more...": {

View file

@ -73,9 +73,9 @@
"Preparing to send logs": "Forbereder afsendelse af logfiler", "Preparing to send logs": "Forbereder afsendelse af logfiler",
"Permission Required": "Tilladelse påkrævet", "Permission Required": "Tilladelse påkrævet",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s. %(monthName)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s. %(monthName)s %(fullYear)s",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s og %(count)s andre", "other": "<Items/> og %(count)s andre",
"one": "%(items)s og en anden" "one": "<Items/> og en anden"
}, },
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
"Please contact your homeserver administrator.": "Kontakt venligst din homeserver administrator.", "Please contact your homeserver administrator.": "Kontakt venligst din homeserver administrator.",

View file

@ -145,9 +145,9 @@
}, },
"Delete Widget": "Widget löschen", "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?", "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": { "<Items/> and %(count)s others": {
"other": "%(items)s und %(count)s andere", "other": "<Items/> und %(count)s andere",
"one": "%(items)s und ein weiteres Raummitglied" "one": "<Items/> und ein weiteres Raummitglied"
}, },
"Restricted": "Eingeschränkt", "Restricted": "Eingeschränkt",
"%(duration)ss": "%(duration)ss", "%(duration)ss": "%(duration)ss",

View file

@ -167,9 +167,9 @@
"Your homeserver has exceeded its user limit.": "Ο διακομιστής σας ξεπέρασε το όριο χρηστών.", "Your homeserver has exceeded its user limit.": "Ο διακομιστής σας ξεπέρασε το όριο χρηστών.",
"Use app": "Χρησιμοποιήστε την εφαρμογή", "Use app": "Χρησιμοποιήστε την εφαρμογή",
"Use app for a better experience": "Χρησιμοποιήστε την εφαρμογή για καλύτερη εμπειρία", "Use app for a better experience": "Χρησιμοποιήστε την εφαρμογή για καλύτερη εμπειρία",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"one": "%(items)s και ένα ακόμα", "one": "<Items/> και ένα ακόμα",
"other": "%(items)s και %(count)s άλλα" "other": "<Items/> και %(count)s άλλα"
}, },
"Ask this user to verify their session, or manually verify it below.": "Ζητήστε από αυτόν τον χρήστη να επιβεβαιώσει την συνεδρία του, ή επιβεβαιώστε την χειροκίνητα παρακάτω.", "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) συνδέθηκε σε μία νέα συνεδρία χωρίς να την επιβεβαιώσει:", "%(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_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.", "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", "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_user1_and_user2": "Inviting %(user1)s and %(user2)s",
"inviting_user_and_n_others": { "inviting_user_and_n_others": {
"other": "Inviting %(user)s and %(count)s 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)", "empty_room_was_name": "Empty room (was %(oldName)s)",
"notifier": { "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>.", "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." "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": { "<Items/> and %(count)s others": {
"other": "%(items)s and %(count)s others", "other": "<Items/> and %(count)s others",
"one": "%(items)s and one other" "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.", "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": { "in_space_and_n_other_spaces": {
"other": "In %(spaceName)s and %(count)s 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.", "in_space": "In %(spaceName)s.",
"name_and_id": "%(name)s (%(userId)s)", "name_and_id": "%(name)s (%(userId)s)",
@ -2571,11 +2560,6 @@
"Public space": "Public space", "Public space": "Public space",
"Private space": "Private space", "Private space": "Private space",
"Private room": "Private room", "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": { "%(count)s people asking to join": {
"other": "%(count)s people asking to join", "other": "%(count)s people asking to join",
"one": "Asking to join" "one": "Asking to join"

View file

@ -91,9 +91,9 @@
"Delete widget": "Forigi fenestraĵon", "Delete widget": "Forigi fenestraĵon",
"Create new room": "Krei novan ĉambron", "Create new room": "Krei novan ĉambron",
"Home": "Hejmo", "Home": "Hejmo",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s kaj %(count)s aliaj", "other": "<Items/> kaj %(count)s aliaj",
"one": "%(items)s kaj unu alia" "one": "<Items/> kaj unu alia"
}, },
"%(items)s and %(lastItem)s": "%(items)s kaj %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s kaj %(lastItem)s",
"collapse": "maletendi", "collapse": "maletendi",

View file

@ -177,9 +177,9 @@
"Delete Widget": "Eliminar accesorio", "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?", "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", "Popout widget": "Abrir accesorio en una ventana emergente",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s y otros %(count)s", "other": "<Items/> y otros %(count)s",
"one": "%(items)s y otro más" "one": "<Items/> y otro más"
}, },
"collapse": "encoger", "collapse": "encoger",
"expand": "desplegar", "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", "Identity server URL does not appear to be a valid identity server": "Isikutuvastusserveri aadress ei tundu viitama kehtivale isikutuvastusserverile",
"Looks good!": "Tundub õige!", "Looks good!": "Tundub õige!",
"Failed to re-authenticate due to a homeserver problem": "Uuesti autentimine ei õnnestunud koduserveri vea tõttu", "Failed to re-authenticate due to a homeserver problem": "Uuesti autentimine ei õnnestunud koduserveri vea tõttu",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s ja %(count)s muud", "other": "<Items/> ja %(count)s muud",
"one": "%(items)s ja üks muu" "one": "<Items/> ja üks muu"
}, },
"%(items)s and %(lastItem)s": "%(items)s ja %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s ja %(lastItem)s",
"Please contact your homeserver administrator.": "Palun võta ühendust koduserveri haldajaga.", "Please contact your homeserver administrator.": "Palun võta ühendust koduserveri haldajaga.",

View file

@ -153,9 +153,9 @@
}, },
"Delete Widget": "Ezabatu trepeta", "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?", "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": { "<Items/> and %(count)s others": {
"other": "%(items)s eta beste %(count)s", "other": "<Items/> eta beste %(count)s",
"one": "%(items)s eta beste bat" "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.", "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", "Replying": "Erantzuten",

View file

@ -1011,10 +1011,9 @@
"IRC display name width": "عرض نمایش نام‌های IRC", "IRC display name width": "عرض نمایش نام‌های IRC",
"This event could not be displayed": "امکان نمایش این رخداد وجود ندارد", "This event could not be displayed": "امکان نمایش این رخداد وجود ندارد",
"Edit message": "ویرایش پیام", "Edit message": "ویرایش پیام",
"%(items)s and %(lastItem)s": "%(items)s و %(lastItem)s", "<Items/> and %(count)s others": {
"%(items)s and %(count)s others": { "one": "<Items/> و یکی دیگر",
"one": "%(items)s و یکی دیگر", "other": "<Items/> و %(count)s دیگر"
"other": "%(items)s و %(count)s دیگر"
}, },
"Clear personal data": "پاک‌کردن داده‌های شخصی", "Clear personal data": "پاک‌کردن داده‌های شخصی",
"Verify your identity to access encrypted messages and prove your identity to others.": "با تائید هویت خود به پیام‌های رمزشده دسترسی یافته و هویت خود را به دیگران ثابت می‌کنید.", "Verify your identity to access encrypted messages and prove your identity to others.": "با تائید هویت خود به پیام‌های رمزشده دسترسی یافته و هویت خود را به دیگران ثابت می‌کنید.",

View file

@ -151,9 +151,9 @@
"expand": "laajenna", "expand": "laajenna",
"collapse": "supista", "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?", "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": { "<Items/> and %(count)s others": {
"other": "%(items)s ja %(count)s muuta", "other": "<Items/> ja %(count)s muuta",
"one": "%(items)s ja yksi muu" "one": "<Items/> ja yksi muu"
}, },
"Sunday": "Sunnuntai", "Sunday": "Sunnuntai",
"Notification targets": "Ilmoituksen kohteet", "Notification targets": "Ilmoituksen kohteet",

View file

@ -142,9 +142,9 @@
"Jump to read receipt": "Aller à laccusé de lecture", "Jump to read receipt": "Aller à laccusé de lecture",
"Delete Widget": "Supprimer le widget", "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 ?", "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": { "<Items/> and %(count)s others": {
"other": "%(items)s et %(count)s autres", "other": "<Items/> et %(count)s autres",
"one": "%(items)s et un autre" "one": "<Items/> et un autre"
}, },
"And %(count)s more...": { "And %(count)s more...": {
"other": "Et %(count)s autres…" "other": "Et %(count)s autres…"

View file

@ -94,9 +94,9 @@
"Delete widget": "Eliminar widget", "Delete widget": "Eliminar widget",
"Create new room": "Crear unha nova sala", "Create new room": "Crear unha nova sala",
"Home": "Inicio", "Home": "Inicio",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s e %(count)s outras", "other": "<Items/> e %(count)s outras",
"one": "%(items)s e outra máis" "one": "<Items/> e outra máis"
}, },
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"collapse": "comprimir", "collapse": "comprimir",

View file

@ -307,10 +307,9 @@
"Vietnam": "וייטנאם", "Vietnam": "וייטנאם",
"Venezuela": "ונצואלה", "Venezuela": "ונצואלה",
"Vatican City": "ותיקן", "Vatican City": "ותיקן",
"%(items)s and %(lastItem)s": "%(items)s ו%(lastItem)s אחרון", "<Items/> and %(count)s others": {
"%(items)s and %(count)s others": { "one": "<Items/> ועוד אחד אחר",
"one": "%(items)s ועוד אחד אחר", "other": "<Items/> ו%(count)s אחרים"
"other": "%(items)s ו%(count)s אחרים"
}, },
"Not Trusted": "לא אמין", "Not Trusted": "לא אמין",
"Ask this user to verify their session, or manually verify it below.": "בקש ממשתמש זה לאמת את ההתחברות שלו, או לאמת אותה באופן ידני למטה.", "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", "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?", "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": { "<Items/> and %(count)s others": {
"other": "%(items)s és még %(count)s másik", "other": "<Items/> és még %(count)s másik",
"one": "%(items)s és még egy másik" "one": "<Items/> és még egy másik"
}, },
"Restricted": "Korlátozott", "Restricted": "Korlátozott",
"%(duration)ss": "%(duration)s mp", "%(duration)ss": "%(duration)s mp",

View file

@ -526,9 +526,9 @@
"Sending": "Mengirim", "Sending": "Mengirim",
"Spaces": "Space", "Spaces": "Space",
"Connecting": "Menghubungkan", "Connecting": "Menghubungkan",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"one": "%(items)s dan satu lainnya", "one": "<Items/> dan satu lainnya",
"other": "%(items)s dan %(count)s lainnya" "other": "<Items/> dan %(count)s lainnya"
}, },
"Disconnect from the identity server <idserver />?": "Putuskan hubungan dari server identitas <idserver />?", "Disconnect from the identity server <idserver />?": "Putuskan hubungan dari server identitas <idserver />?",
"Disconnect identity server": "Putuskan hubungan server identitas", "Disconnect identity server": "Putuskan hubungan server identitas",

View file

@ -686,10 +686,9 @@
"Enable desktop notifications": "Virkja tilkynningar á skjáborði", "Enable desktop notifications": "Virkja tilkynningar á skjáborði",
"Don't miss a reply": "Ekki missa af svari", "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", "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/> and %(count)s others": {
"%(items)s and %(count)s others": { "one": "<Items/> og einn til viðbótar",
"one": "%(items)s og einn til viðbótar", "other": "<Items/> og %(count)s til viðbótar"
"other": "%(items)s og %(count)s til viðbótar"
}, },
"Private space": "Einkasvæði", "Private space": "Einkasvæði",
"Mentions only": "Aðeins minnst á", "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?", "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", "Delete widget": "Elimina widget",
"Home": "Pagina iniziale", "Home": "Pagina iniziale",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s e altri %(count)s", "other": "<Items/> e altri %(count)s",
"one": "%(items)s e un altro" "one": "<Items/> e un altro"
}, },
"%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s e %(lastItem)s",
"collapse": "richiudi", "collapse": "richiudi",

View file

@ -135,9 +135,9 @@
"Delete widget": "ウィジェットを削除", "Delete widget": "ウィジェットを削除",
"Popout widget": "ウィジェットをポップアウト", "Popout widget": "ウィジェットをポップアウト",
"Home": "ホーム", "Home": "ホーム",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)sと他%(count)s人", "other": "<Items/>と他%(count)s人",
"one": "%(items)sともう1人" "one": "<Items/>ともう1人"
}, },
"%(items)s and %(lastItem)s": "%(items)s, %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s, %(lastItem)s",
"collapse": "折りたたむ", "collapse": "折りたたむ",

View file

@ -115,9 +115,9 @@
"Set up Secure Messages": "Sbadu iznan iɣelsanen", "Set up Secure Messages": "Sbadu iznan iɣelsanen",
"Logs sent": "Iɣmisen ttewaznen", "Logs sent": "Iɣmisen ttewaznen",
"Not Trusted": "Ur yettwattkal ara", "Not Trusted": "Ur yettwattkal ara",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s d %(count)s wiyaḍ", "other": "<Items/> d %(count)s wiyaḍ",
"one": "%(items)s d wayeḍ-nniḍen" "one": "<Items/> d wayeḍ-nniḍen"
}, },
"%(items)s and %(lastItem)s": "%(items)s d %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s d %(lastItem)s",
"Encryption upgrade available": "Yella uleqqem n uwgelhen", "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.": "자기 자신을 강등하는 것은 되돌릴 수 없고, 자신이 마지막으로 이 방에서 특권을 가진 사용자라면 다시 특권을 얻는 건 불가능합니다.", "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": "읽은 기록으로 건너뛰기", "Jump to read receipt": "읽은 기록으로 건너뛰기",
"Share room": "방 공유하기", "Share room": "방 공유하기",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"one": "%(items)s님 외 한 명", "one": "<Items/>님 외 한 명",
"other": "%(items)s님 외 %(count)s명" "other": "<Items/>님 외 %(count)s명"
}, },
"Permission Required": "권한 필요", "Permission Required": "권한 필요",
"Copied!": "복사했습니다!", "Copied!": "복사했습니다!",

View file

@ -1190,10 +1190,9 @@
"other": "%(spaceName)s ແລະ %(count)s ອື່ນໆ" "other": "%(spaceName)s ແລະ %(count)s ອື່ນໆ"
}, },
"%(space1Name)s and %(space2Name)s": "%(space1Name)s ແລະ %(space2Name)s", "%(space1Name)s and %(space2Name)s": "%(space1Name)s ແລະ %(space2Name)s",
"%(items)s and %(lastItem)s": "%(items)s ແລະ %(lastItem)s", "<Items/> and %(count)s others": {
"%(items)s and %(count)s others": { "one": "<Items/> ແລະ ອີກນຶ່ງລາຍການ",
"one": "%(items)s ແລະ ອີກນຶ່ງລາຍການ", "other": "<Items/> ແລະ %(count)s ອື່ນໆ"
"other": "%(items)s ແລະ %(count)s ອື່ນໆ"
}, },
"Email (optional)": "ອີເມວ (ທາງເລືອກ)", "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>.", "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!", "No backup found!": "Nerasta jokios atsarginės kopijos!",
"Failed to decrypt %(failedCount)s sessions!": "Nepavyko iššifruoti %(failedCount)s seansų!", "Failed to decrypt %(failedCount)s sessions!": "Nepavyko iššifruoti %(failedCount)s seansų!",
"Explore rooms": "Žvalgyti kambarius", "Explore rooms": "Žvalgyti kambarius",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s ir %(count)s kiti(-ų)", "other": "<Items/> ir %(count)s kiti(-ų)",
"one": "%(items)s ir dar vienas" "one": "<Items/> ir dar vienas"
}, },
"%(items)s and %(lastItem)s": "%(items)s ir %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s ir %(lastItem)s",
"General": "Bendrieji", "General": "Bendrieji",

View file

@ -158,9 +158,9 @@
"other": "Un par %(count)s vairāk..." "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.", "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": { "<Items/> and %(count)s others": {
"one": "%(items)s un viens cits", "one": "<Items/> un viens cits",
"other": "%(items)s un %(count)s citus" "other": "<Items/> un %(count)s citus"
}, },
"Sunday": "Svētdiena", "Sunday": "Svētdiena",
"Notification targets": "Paziņojumu adresāti", "Notification targets": "Paziņojumu adresāti",

View file

@ -248,9 +248,9 @@
"Upgrade your encryption": "Oppgrader krypteringen din", "Upgrade your encryption": "Oppgrader krypteringen din",
"Verify this session": "Verifiser denne økten", "Verify this session": "Verifiser denne økten",
"Not Trusted": "Ikke betrodd", "Not Trusted": "Ikke betrodd",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s og %(count)s andre", "other": "<Items/> og %(count)s andre",
"one": "%(items)s og én annen" "one": "<Items/> og én annen"
}, },
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
"Show more": "Vis mer", "Show more": "Vis mer",

View file

@ -151,9 +151,9 @@
"Replying": "Aan het beantwoorden", "Replying": "Aan het beantwoorden",
"Unnamed room": "Naamloze kamer", "Unnamed room": "Naamloze kamer",
"Banned by %(displayName)s": "Verbannen door %(displayName)s", "Banned by %(displayName)s": "Verbannen door %(displayName)s",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s en %(count)s andere", "other": "<Items/> en %(count)s andere",
"one": "%(items)s en één ander" "one": "<Items/> en één ander"
}, },
"collapse": "dichtvouwen", "collapse": "dichtvouwen",
"expand": "uitvouwen", "expand": "uitvouwen",

View file

@ -109,9 +109,9 @@
"Delete widget": "Slett widgeten", "Delete widget": "Slett widgeten",
"Create new room": "Lag nytt rom", "Create new room": "Lag nytt rom",
"Home": "Heim", "Home": "Heim",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s og %(count)s til", "other": "<Items/> og %(count)s til",
"one": "%(items)s og ein til" "one": "<Items/> og ein til"
}, },
"%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s og %(lastItem)s",
"collapse": "Slå saman", "collapse": "Slå saman",

View file

@ -210,9 +210,9 @@
"other": "I %(count)s więcej…" "other": "I %(count)s więcej…"
}, },
"Delete Backup": "Usuń kopię zapasową", "Delete Backup": "Usuń kopię zapasową",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s i %(count)s innych", "other": "<Items/> i %(count)s innych",
"one": "%(items)s i jedna inna osoba" "one": "<Items/> i jedna inna osoba"
}, },
"Add some now": "Dodaj teraz kilka", "Add some now": "Dodaj teraz kilka",
"Continue With Encryption Disabled": "Kontynuuj Z Wyłączonym Szyfrowaniem", "Continue With Encryption Disabled": "Kontynuuj Z Wyłączonym Szyfrowaniem",

View file

@ -151,9 +151,9 @@
"Delete Widget": "Apagar widget", "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?", "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", "Delete widget": "Remover widget",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s e %(count)s outras", "other": "<Items/> e %(count)s outras",
"one": "%(items)s e uma outra" "one": "<Items/> e uma outra"
}, },
"collapse": "recolher", "collapse": "recolher",
"expand": "expandir", "expand": "expandir",

View file

@ -145,9 +145,9 @@
}, },
"Delete Widget": "Удалить виджет", "Delete Widget": "Удалить виджет",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this 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": { "<Items/> and %(count)s others": {
"other": "%(items)s и ещё %(count)s участника(-ов)", "other": "<Items/> и ещё %(count)s участника(-ов)",
"one": "%(items)s и ещё кто-то" "one": "<Items/> и ещё кто-то"
}, },
"Restricted": "Ограниченный пользователь", "Restricted": "Ограниченный пользователь",
"%(duration)ss": "%(duration)s сек", "%(duration)ss": "%(duration)s сек",

View file

@ -85,9 +85,9 @@
"Delete widget": "Vymazať widget", "Delete widget": "Vymazať widget",
"Create new room": "Vytvoriť novú miestnosť", "Create new room": "Vytvoriť novú miestnosť",
"Home": "Domov", "Home": "Domov",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s a %(count)s ďalší", "other": "<Items/> a %(count)s ďalší",
"one": "%(items)s a jeden ďalší" "one": "<Items/> a jeden ďalší"
}, },
"%(items)s and %(lastItem)s": "%(items)s a tiež %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s a tiež %(lastItem)s",
"Custom level": "Vlastná úroveň", "Custom level": "Vlastná úroveň",

View file

@ -94,9 +94,9 @@
"Something went wrong!": "Diçka shkoi ters!", "Something went wrong!": "Diçka shkoi ters!",
"Create new room": "Krijoni dhomë të re", "Create new room": "Krijoni dhomë të re",
"Home": "Kreu", "Home": "Kreu",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s dhe %(count)s të tjerë", "other": "<Items/> dhe %(count)s të tjerë",
"one": "%(items)s dhe një tjetër" "one": "<Items/> dhe një tjetër"
}, },
"%(items)s and %(lastItem)s": "%(items)s dhe %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s dhe %(lastItem)s",
"collapse": "tkurre", "collapse": "tkurre",

View file

@ -94,9 +94,9 @@
"Delete widget": "Обриши виџет", "Delete widget": "Обриши виџет",
"Create new room": "Направи нову собу", "Create new room": "Направи нову собу",
"Home": "Почетна", "Home": "Почетна",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s и %(count)s других", "other": "<Items/> и %(count)s других",
"one": "%(items)s и још један" "one": "<Items/> и још један"
}, },
"%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s", "%(items)s and %(lastItem)s": "%(items)s и %(lastItem)s",
"collapse": "скупи", "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.", "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", "Confirm Removal": "Bekräfta borttagning",
"Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar", "Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s och %(count)s till", "other": "<Items/> och %(count)s till",
"one": "%(items)s och en till" "one": "<Items/> och en till"
}, },
"collapse": "fäll ihop", "collapse": "fäll ihop",
"expand": "fäll ut", "expand": "fäll ut",

View file

@ -228,9 +228,9 @@
"Your password has been reset.": "Parolanız sıfırlandı.", "Your password has been reset.": "Parolanız sıfırlandı.",
"General failure": "Genel başarısızlık", "General failure": "Genel başarısızlık",
"Create account": "Yeni hesap", "Create account": "Yeni hesap",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s ve diğer %(count)s", "other": "<Items/> ve diğer %(count)s",
"one": "%(items)s ve bir diğeri" "one": "<Items/> ve bir diğeri"
}, },
"Clear personal data": "Kişisel veri temizle", "Clear personal data": "Kişisel veri temizle",
"That matches!": "Eşleşti!", "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) починає новий сеанс без його звірення:", "%(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.": "Попросіть цього користувача звірити сеанс, або звірте його власноруч унизу.", "Ask this user to verify their session, or manually verify it below.": "Попросіть цього користувача звірити сеанс, або звірте його власноруч унизу.",
"Not Trusted": "Не довірений", "Not Trusted": "Не довірений",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s та ще %(count)s учасників", "other": "<Items/> та ще %(count)s учасників",
"one": "%(items)s і ще хтось" "one": "<Items/> і ще хтось"
}, },
"Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.", "Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.",
"Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив одне із своїх обмежень ресурсів.", "Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив одне із своїх обмежень ресурсів.",

View file

@ -30,9 +30,9 @@
"Restricted": "Bị hạn chế", "Restricted": "Bị hạn chế",
"Moderator": "Điều phối viên", "Moderator": "Điều phối viên",
"Reason": "Lý do", "Reason": "Lý do",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s và %(count)s mục khác", "other": "<Items/> và %(count)s mục khác",
"one": "%(items)s và một mục khác" "one": "<Items/> và một mục khác"
}, },
"%(items)s and %(lastItem)s": "%(items)s và %(lastItem)s", "%(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.", "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", "Restricted": "Beperkten toegank",
"Moderator": "Moderator", "Moderator": "Moderator",
"Reason": "Reedn", "Reason": "Reedn",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s en %(count)s andere", "other": "<Items/> en %(count)s andere",
"one": "%(items)s en één ander" "one": "<Items/> en één ander"
}, },
"%(items)s and %(lastItem)s": "%(items)s en %(lastItem)s", "%(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.", "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": "跳到阅读回执", "Jump to read receipt": "跳到阅读回执",
"Unnamed room": "未命名的房间", "Unnamed room": "未命名的房间",
"Delete Widget": "删除挂件", "Delete Widget": "删除挂件",
"%(items)s and %(count)s others": { "<Items/> and %(count)s others": {
"other": "%(items)s 和其他 %(count)s 人", "other": "<Items/> 和其他 %(count)s 人",
"one": "%(items)s 与另一个人" "one": "<Items/> 与另一个人"
}, },
"collapse": "折叠", "collapse": "折叠",
"expand": "展开", "expand": "展开",

View file

@ -150,9 +150,9 @@
"Banned by %(displayName)s": "被 %(displayName)s 封鎖", "Banned by %(displayName)s": "被 %(displayName)s 封鎖",
"Delete Widget": "刪除小工具", "Delete Widget": "刪除小工具",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this 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": { "<Items/> and %(count)s others": {
"other": "%(items)s 與其他 %(count)s 個人", "other": "<Items/> 與其他 %(count)s 個人",
"one": "%(items)s 與另一個人" "one": "<Items/> 與另一個人"
}, },
"collapse": "收折", "collapse": "收折",
"expand": "展開", "expand": "展開",

View file

@ -18,7 +18,7 @@ limitations under the License.
import { ReactElement, ReactNode } from "react"; import { ReactElement, ReactNode } from "react";
import { useIdColorHash } from "@vector-im/compound-web"; import { useIdColorHash } from "@vector-im/compound-web";
import { _t, getCurrentLanguage } from "../languageHandler"; import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler";
import { jsxJoin } from "./ReactUtils"; import { jsxJoin } from "./ReactUtils";
const locale = getCurrentLanguage(); const locale = getCurrentLanguage();
@ -92,34 +92,42 @@ export function getUserNameColorClass(userId: string): string {
* @returns {string} a string constructed by joining `items` with a comma * @returns {string} a string constructed by joining `items` with a comma
* between each item, but with the last item appended as " and [lastItem]". * between each item, but with the last item appended as " and [lastItem]".
*/ */
export function formatCommaSeparatedList(items: string[], itemLimit?: number): string; export function formatList(items: string[], itemLimit?: number, includeCount?: boolean): string;
export function formatCommaSeparatedList(items: ReactElement[], itemLimit?: number): ReactElement; export function formatList(items: ReactElement[], itemLimit?: number, includeCount?: boolean): ReactElement;
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode; export function formatList(items: ReactNode[], itemLimit?: number, includeCount?: boolean): ReactNode;
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode { export function formatList(items: ReactNode[], itemLimit = items.length, includeCount = false): ReactNode {
const remaining = itemLimit === undefined ? 0 : Math.max(items.length - itemLimit, 0); let remaining = Math.max(items.length - itemLimit, 0);
if (items.length === 0) { if (items.length <= 1) {
return ""; return items[0] ?? "";
} else if (items.length === 1) { }
return items[0];
} else { const formatter = new Intl.ListFormat(getUserLanguage(), { style: "long", type: "conjunction" });
let lastItem; if (remaining > 0) {
if (remaining > 0) { if (includeCount) {
items = items.slice(0, itemLimit); itemLimit--;
} else { remaining++;
lastItem = items.pop();
} }
let joinedItems; items = items.slice(0, itemLimit);
let joinedItems: ReactNode;
if (items.every((e) => typeof e === "string")) { if (items.every((e) => typeof e === "string")) {
joinedItems = items.join(", "); joinedItems = items.join(", ");
} else { } else {
joinedItems = jsxJoin(items, ", "); joinedItems = jsxJoin(items, ", ");
} }
if (remaining > 0) { return _t("<Items/> and %(count)s others", { count: remaining }, { Items: () => joinedItems });
return _t("%(items)s and %(count)s others", { items: joinedItems, count: remaining });
} else {
return _t("%(items)s and %(lastItem)s", { items: joinedItems, lastItem });
}
} }
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 SpaceStore from "../stores/spaces/SpaceStore";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import DMRoomMap from "./DMRoomMap"; import DMRoomMap from "./DMRoomMap";
import { formatList } from "./FormattingUtils";
export interface RoomContextDetails { export interface RoomContextDetails {
details: string | null; details: string | null;
@ -39,7 +40,7 @@ export function roomContextDetails(room: Room): RoomContextDetails | null {
const space1Name = room.client.getRoom(parent)?.name; const space1Name = room.client.getRoom(parent)?.name;
const space2Name = room.client.getRoom(secondParent)?.name; const space2Name = room.client.getRoom(secondParent)?.name;
return { return {
details: _t("%(space1Name)s and %(space2Name)s", { space1Name, space2Name }), details: formatList([space1Name ?? "", space2Name ?? ""]),
ariaLabel: _t("in_space1_and_space2", { space1Name, space2Name }), ariaLabel: _t("in_space1_and_space2", { space1Name, space2Name }),
}; };
} else if (parent) { } else if (parent) {
@ -47,7 +48,7 @@ export function roomContextDetails(room: Room): RoomContextDetails | null {
const count = otherParents.length; const count = otherParents.length;
if (count > 0) { if (count > 0) {
return { 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 }), 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 EventListSummary from "../../../../src/components/views/elements/EventListSummary";
import { Layout } from "../../../../src/settings/enums/Layout"; import { Layout } from "../../../../src/settings/enums/Layout";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import * as languageHandler from "../../../../src/languageHandler";
describe("EventListSummary", function () { describe("EventListSummary", function () {
const roomId = "!room:server.org"; const roomId = "!room:server.org";
@ -136,6 +137,7 @@ describe("EventListSummary", function () {
beforeEach(function () { beforeEach(function () {
jest.clearAllMocks(); jest.clearAllMocks();
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
}); });
afterAll(() => { afterAll(() => {

View file

@ -40,6 +40,7 @@ import {
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
import * as languageHandler from "../../../../src/languageHandler";
const CHECKED = "mx_PollOption_checked"; const CHECKED = "mx_PollOption_checked";
const userId = "@me:example.com"; const userId = "@me:example.com";
@ -58,6 +59,7 @@ describe("MPollBody", () => {
mockClient.getRoom.mockReturnValue(null); mockClient.getRoom.mockReturnValue(null);
mockClient.relations.mockResolvedValue({ events: [] }); mockClient.relations.mockResolvedValue({ events: [] });
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
}); });
it("finds no votes if there are none", () => { it("finds no votes if there are none", () => {

View file

@ -39,6 +39,7 @@ import {
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
mockClientMethodsUser, mockClientMethodsUser,
} from "../../../test-utils"; } from "../../../test-utils";
import * as languageHandler from "../../../../src/languageHandler";
describe("RoomKnocksBar", () => { describe("RoomKnocksBar", () => {
const userId = "@alice:example.org"; const userId = "@alice:example.org";
@ -127,6 +128,7 @@ describe("RoomKnocksBar", () => {
jest.spyOn(state, "hasSufficientPowerLevelFor").mockReturnValue(true); jest.spyOn(state, "hasSufficientPowerLevelFor").mockReturnValue(true);
jest.spyOn(Modal, "createDialog"); jest.spyOn(Modal, "createDialog");
jest.spyOn(dis, "dispatch"); jest.spyOn(dis, "dispatch");
jest.spyOn(languageHandler, "getUserLanguage").mockReturnValue("en-GB");
}); });
it("does not render if user can neither approve nor deny", () => { 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]), new Set([parent1.roomId, parent2.roomId, parent3.roomId]),
); );
const res = roomContextDetails(room); const res = roomContextDetails(room);
expect(res!.details).toMatchInlineSnapshot(`"Alpha and 1 other"`); expect(res!.details).toMatchInlineSnapshot(`"Alpha and one other"`);
expect(res!.ariaLabel).toMatchInlineSnapshot(`"In Alpha and 1 other space."`); expect(res!.ariaLabel).toMatchInlineSnapshot(`"In Alpha and one other space."`);
}); });
}); });

View file

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