Make valid-i18n-keys rule strict and fix most exceptions

This commit is contained in:
Jamie Kyle 2023-03-29 10:15:54 -07:00 committed by GitHub
parent 18a6da310f
commit 11cfcb4e32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 796 additions and 687 deletions

View file

@ -14,9 +14,11 @@ const messagesCacheKey = hashSum.digest('hex');
function isI18nCall(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'i18n'
(node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'i18n') ||
(node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'i18n')
);
}
@ -36,20 +38,7 @@ function valueToMessageKey(node) {
if (isStringLiteral(node)) {
return node.value;
}
if (node.type !== 'TemplateLiteral') {
return null;
}
if (node.quasis.length === 1) {
return node.quasis[0].value.cooked;
}
const parts = node.quasis.map(element => {
return element.value.cooked;
});
return new RegExp(`^${parts.join('(.*)')}$`);
return null;
}
function getI18nCallMessageKey(node) {
@ -80,24 +69,11 @@ function getIntlElementMessageKey(node) {
let value = idAttribute.value;
if (value.type === 'JSXExpressionContainer') {
value = value.expression;
}
return valueToMessageKey(value);
}
function isValidMessageKey(key) {
if (typeof key === 'string') {
if (Object.hasOwn(messages, key)) {
return true;
}
} else if (key instanceof RegExp) {
if (messageKeys.some(k => key.test(k))) {
return true;
}
}
return false;
return Object.hasOwn(messages, key);
}
module.exports = {

View file

@ -27,27 +27,68 @@ ruleTester.run('valid-i18n-keys', rule, {
options: [{ messagesCacheKey }],
},
{
code: 'i18n(`AddCaptionModal__${title}`)',
code: `window.i18n("AddCaptionModal__title")`,
options: [{ messagesCacheKey }],
},
{
code: `let jsx = <Intl id="AddCaptionModal__title"/>`,
options: [{ messagesCacheKey }],
},
],
invalid: [
{
code: 'i18n(`AddCaptionModal__${title}`)',
options: [{ messagesCacheKey }],
errors: [
{
message: "i18n()'s first argument should always be a literal string",
type: 'CallExpression',
},
],
},
{
code: 'window.i18n(`AddCaptionModal__${title}`)',
options: [{ messagesCacheKey }],
errors: [
{
message: "i18n()'s first argument should always be a literal string",
type: 'CallExpression',
},
],
},
{
code: `let jsx = <Intl id={"AddCaptionModal__title"}/>`,
options: [{ messagesCacheKey }],
errors: [
{
message:
"<Intl> must always be provided an 'id' attribute with a literal string",
type: 'JSXOpeningElement',
},
],
},
{
code: 'let jsx = <Intl id={`AddCaptionModal__title`}/>',
options: [{ messagesCacheKey }],
errors: [
{
message:
"<Intl> must always be provided an 'id' attribute with a literal string",
type: 'JSXOpeningElement',
},
],
},
{
code: 'let jsx = <Intl id={`AddCaptionModal__${title}`}/>',
options: [{ messagesCacheKey }],
errors: [
{
message:
"<Intl> must always be provided an 'id' attribute with a literal string",
type: 'JSXOpeningElement',
},
],
},
],
invalid: [
{
code: `i18n("THIS_KEY_SHOULD_NEVER_EXIST")`,
options: [{ messagesCacheKey }],

View file

@ -604,19 +604,23 @@ export function Headers(): JSX.Element {
rows={[
{
type: RowType.Header,
i18nKey: 'conversationsHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('conversationsHeader'),
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('messagesHeader'),
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByUsernameHeader'),
},
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
},
]}
/>
@ -629,7 +633,8 @@ export function FindByPhoneNumber(): JSX.Element {
rows={[
{
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
},
{
type: RowType.StartNewConversation,
@ -673,7 +678,8 @@ export function FindByUsername(): JSX.Element {
rows={[
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByUsernameHeader'),
},
{
type: RowType.UsernameSearchResult,
@ -745,7 +751,8 @@ export function KitchenSink(): JSX.Element {
},
{
type: RowType.Header,
i18nKey: 'contactsHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('contactsHeader'),
},
{
type: RowType.Contact,
@ -753,7 +760,8 @@ export function KitchenSink(): JSX.Element {
},
{
type: RowType.Header,
i18nKey: 'messagesHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('messagesHeader'),
},
{
type: RowType.Conversation,
@ -765,7 +773,8 @@ export function KitchenSink(): JSX.Element {
},
{
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByUsernameHeader'),
},
{
type: RowType.UsernameSearchResult,

View file

@ -103,9 +103,17 @@ type MessageRowType = {
type HeaderRowType = {
type: RowType.Header;
i18nKey: string;
getHeaderText: (i18n: LocalizerType) => string;
};
// Exported for tests across multiple files
export function _testHeaderText(row: Row | void): string | null {
if (row?.type === RowType.Header) {
return row.getHeaderText(((key: string) => key) as LocalizerType);
}
return null;
}
type SearchResultsLoadingFakeHeaderType = {
type: RowType.SearchResultsLoadingFakeHeader;
};
@ -375,18 +383,18 @@ export function ConversationList({
/>
);
break;
case RowType.Header:
case RowType.Header: {
const headerText = row.getHeaderText(i18n);
result = (
<div
className="module-conversation-list__item--header"
// eslint-disable-next-line local-rules/valid-i18n-keys
aria-label={i18n(row.i18nKey)}
aria-label={headerText}
>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
{i18n(row.i18nKey)}
{headerText}
</div>
);
break;
}
case RowType.MessageSearchResult:
result = <>{renderMessageSearchResult?.(row.messageId)}</>;
break;

View file

@ -7,6 +7,7 @@ import type { ConversationType } from '../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { GroupDialog } from './GroupDialog';
import { sortByTitle } from '../util/sortByTitle';
import { missingCaseError } from '../util';
export type DataPropsType = {
conversationId: string;
@ -70,8 +71,6 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
const keepHistory = hasMigrated
? i18n('GroupV1--Migration--info--keep-history')
: i18n('GroupV1--Migration--migrate--keep-history');
const migrationKey = hasMigrated ? 'after' : 'before';
const droppedMembersKey = `GroupV1--Migration--info--removed--${migrationKey}`;
let primaryButtonText: string;
let onClickPrimaryButton: () => void;
@ -116,14 +115,16 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
getPreferredBadge,
i18n,
members: invitedMembers,
prefix: 'GroupV1--Migration--info--invited',
hasMigrated,
kind: 'invited',
theme,
})}
{renderMembers({
getPreferredBadge,
i18n,
members: droppedMembers,
prefix: droppedMembersKey,
hasMigrated,
kind: 'dropped',
theme,
})}
</>
@ -136,26 +137,49 @@ function renderMembers({
getPreferredBadge,
i18n,
members,
prefix,
hasMigrated,
kind,
theme,
}: Readonly<{
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
members: Array<ConversationType>;
prefix: string;
hasMigrated: boolean;
kind: 'invited' | 'dropped';
theme: ThemeType;
}>): React.ReactNode {
if (!members.length) {
return null;
}
const postfix = members.length === 1 ? '--one' : '--many';
const key = `${prefix}${postfix}`;
let text: string;
switch (kind) {
case 'invited':
text =
members.length === 1
? i18n('GroupV1--Migration--info--invited--one')
: i18n('GroupV1--Migration--info--invited--many');
break;
case 'dropped':
if (hasMigrated) {
text =
members.length === 1
? i18n('GroupV1--Migration--info--removed--before--one')
: i18n('GroupV1--Migration--info--removed--before--many');
} else {
text =
members.length === 1
? i18n('GroupV1--Migration--info--removed--after--one')
: i18n('GroupV1--Migration--info--removed--after--many');
}
break;
default:
throw missingCaseError(kind);
}
return (
<>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
<GroupDialog.Paragraph>{i18n(key)}</GroupDialog.Paragraph>
<GroupDialog.Paragraph>{text}</GroupDialog.Paragraph>
<GroupDialog.Contacts
contacts={sortByTitle(members)}
getPreferredBadge={getPreferredBadge}

View file

@ -71,24 +71,29 @@ export class Intl extends React.Component<Props> {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public override render() {
const { components, id, i18n, renderText = defaultRenderText } = this.props;
const {
components,
id,
// Indirection for linter/migration tooling
i18n: localizer,
renderText = defaultRenderText,
} = this.props;
if (!id) {
log.error('Error: Intl id prop not provided');
return null;
}
if (!i18n.isLegacyFormat(id)) {
if (!localizer.isLegacyFormat(id)) {
strictAssert(
!Array.isArray(components),
`components cannot be an array for ICU message ${id}`
);
const intl = i18n.getIntl();
const intl = localizer.getIntl();
return intl.formatMessage({ id }, components);
}
// eslint-disable-next-line local-rules/valid-i18n-keys
const text = i18n(id);
const text = localizer(id);
const results: Array<
string | JSX.Element | Array<string | JSX.Element> | null
> = [];

View file

@ -339,7 +339,7 @@ export function SendStoryModal({
<EditMyStoryPrivacy
hasDisclaimerAbove
i18n={i18n}
learnMore="SendStoryModal__privacy-disclaimer"
kind="privacy"
myStories={stagedMyStories}
signalConnectionsCount={signalConnections.length}
onClickExclude={() => {

View file

@ -627,7 +627,7 @@ export function DistributionListSettingsModal({
{isMyStory && (
<EditMyStoryPrivacy
i18n={i18n}
learnMore="StoriesSettings__mine__disclaimer"
kind="mine"
myStories={listToEdit}
onClickExclude={() => {
setPage(Page.HideStoryFrom);
@ -791,7 +791,7 @@ function CheckboxRender({
type EditMyStoryPrivacyPropsType = {
hasDisclaimerAbove?: boolean;
i18n: LocalizerType;
learnMore: string;
kind: 'privacy' | 'mine';
myStories: StoryDistributionListWithMembersDataType;
onClickExclude: () => unknown;
onClickOnlyShareWith: () => unknown;
@ -805,7 +805,7 @@ type EditMyStoryPrivacyPropsType = {
export function EditMyStoryPrivacy({
hasDisclaimerAbove,
i18n,
learnMore,
kind,
myStories,
onClickExclude,
onClickOnlyShareWith,
@ -814,24 +814,30 @@ export function EditMyStoryPrivacy({
toggleSignalConnectionsModal,
signalConnectionsCount,
}: EditMyStoryPrivacyPropsType): JSX.Element {
const learnMore = (
<button
className="StoriesSettingsModal__disclaimer__learn-more"
onClick={toggleSignalConnectionsModal}
type="button"
>
{i18n('StoriesSettings__mine__disclaimer--learn-more')}
</button>
);
const disclaimerElement = (
<div className="StoriesSettingsModal__disclaimer">
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
<Intl
components={{
learnMore: (
<button
className="StoriesSettingsModal__disclaimer__learn-more"
onClick={toggleSignalConnectionsModal}
type="button"
>
{i18n('StoriesSettings__mine__disclaimer--learn-more')}
</button>
),
}}
i18n={i18n}
id={learnMore}
/>
{kind === 'mine' ? (
<Intl
components={{ learnMore }}
i18n={i18n}
id="StoriesSettings__mine__disclaimer"
/>
) : (
<Intl
components={{ learnMore }}
i18n={i18n}
id="SendStoryModal__privacy-disclaimer"
/>
)}
</div>
);

View file

@ -40,10 +40,9 @@ export function ToastManager({
if (toastType === ToastType.AddingUserToGroup) {
return (
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
{i18n(
'AddUserToAnotherGroupModal__toast--adding-user-to-group',
toast.parameters
)}
{i18n('AddUserToAnotherGroupModal__toast--adding-user-to-group', {
...toast.parameters,
})}
</Toast>
);
}
@ -107,7 +106,9 @@ export function ToastManager({
if (toastType === ToastType.CannotStartGroupCall) {
return (
<Toast onClose={hideToast}>
{i18n('GroupV2--cannot-start-group-call', toast.parameters)}
{i18n('GroupV2--cannot-start-group-call', {
...toast.parameters,
})}
</Toast>
);
}
@ -344,10 +345,9 @@ export function ToastManager({
if (toastType === ToastType.UserAddedToGroup) {
return (
<Toast onClose={hideToast}>
{i18n(
'AddUserToAnotherGroupModal__toast--user-added-to-group',
toast.parameters
)}
{i18n('AddUserToAnotherGroupModal__toast--user-added-to-group', {
...toast.parameters,
})}
</Toast>
);
}

View file

@ -6,7 +6,6 @@ import React from 'react';
import moment from 'moment';
import { Modal } from './Modal';
import type { IntlComponentsType } from './Intl';
import { Intl } from './Intl';
import { Emojify } from './conversation/Emojify';
import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
@ -19,61 +18,46 @@ export type PropsType = {
type ReleaseNotesType = {
date: Date;
version: string;
features: Array<{ key: string; components: IntlComponentsType }>;
features: Array<JSX.Element>;
};
const renderText: RenderTextCallbackType = ({ key, text }) => (
<Emojify key={key} text={text} />
);
const releaseNotes: ReleaseNotesType = {
date: new Date(window.getBuildCreation?.() || Date.now()),
version: window.getVersion?.(),
features: [
{
key: 'icu:WhatsNew__v6.12--0',
components: {},
},
{
key: 'icu:WhatsNew__v6.12--1',
components: {},
},
],
};
export function WhatsNewModal({
i18n,
hideWhatsNewModal,
}: PropsType): JSX.Element {
let contentNode: ReactChild;
const releaseNotes: ReleaseNotesType = {
date: new Date(window.getBuildCreation?.() || Date.now()),
version: window.getVersion?.(),
features: [
<Intl
i18n={i18n}
id="icu:WhatsNew__v6.12--0"
renderText={renderText}
components={{}}
/>,
<Intl
i18n={i18n}
id="icu:WhatsNew__v6.12--1"
renderText={renderText}
components={{}}
/>,
],
};
if (releaseNotes.features.length === 1) {
const { key, components } = releaseNotes.features[0];
contentNode = (
<p>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
<Intl
i18n={i18n}
id={key}
renderText={renderText}
components={components}
/>
</p>
);
contentNode = <p>{releaseNotes.features[0]}</p>;
} else {
contentNode = (
<ul>
{releaseNotes.features.map(({ key, components }) => (
<li key={key}>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
<Intl
i18n={i18n}
id={key}
renderText={renderText}
components={components}
/>
</li>
))}
{releaseNotes.features.map(element => {
return <li key={element.props.id}>{element}</li>;
})}
</ul>
);
}

View file

@ -60,16 +60,8 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
i18n('GroupV1--Migration--invited--you')
) : (
<>
{renderUsers(
invitedMembers,
i18n,
'GroupV1--Migration--invited'
)}
{renderUsers(
droppedMembers,
i18n,
'GroupV1--Migration--removed'
)}
{renderUsers(invitedMembers, i18n, 'invited')}
{renderUsers(droppedMembers, i18n, 'removed')}
</>
)}
</p>
@ -106,31 +98,52 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
function renderUsers(
members: Array<ConversationType>,
i18n: LocalizerType,
keyPrefix: string
kind: 'invited' | 'removed'
): React.ReactElement | null {
if (!members || members.length === 0) {
return null;
}
if (members.length === 1) {
const contact = <ContactName title={members[0].title} />;
return (
<p>
<Intl
i18n={i18n}
id={`${keyPrefix}--one`}
components={{
contact: <ContactName title={members[0].title} />,
}}
/>
{kind === 'invited' && (
<Intl
i18n={i18n}
id="GroupV1--Migration--invited--one"
components={{ contact }}
/>
)}
{kind === 'removed' && (
<Intl
i18n={i18n}
id="GroupV1--Migration--removed--one"
components={{ contact }}
/>
)}
</p>
);
}
const count = members.length.toString();
return (
<p>
{i18n(`${keyPrefix}--many`, {
count: members.length.toString(),
})}
{kind === 'invited' && members.length > 1 && (
<Intl
i18n={i18n}
id="GroupV1--Migration--invited--many"
components={{ count }}
/>
)}
{kind === 'removed' && members.length > 1 && (
<Intl
i18n={i18n}
id="GroupV1--Migration--removed--many"
components={{ count }}
/>
)}
</p>
);
}

View file

@ -40,6 +40,26 @@ export function MandatoryProfileSharingActions({
}: Props): JSX.Element {
const [mrState, setMrState] = React.useState(MessageRequestState.default);
const firstNameContact = (
<strong
key="name"
className="module-message-request-actions__message__name"
>
<ContactName firstName={firstName} title={title} preferFirstName />
</strong>
);
const learnMore = (
<a
href="https://support.signal.org/hc/articles/360007459591"
target="_blank"
rel="noreferrer"
className="module-message-request-actions__message__learn-more"
>
{i18n('MessageRequests--learn-more')}
</a>
);
return (
<>
{mrState !== MessageRequestState.default ? (
@ -62,34 +82,19 @@ export function MandatoryProfileSharingActions({
) : null}
<div className="module-message-request-actions">
<p className="module-message-request-actions__message">
<Intl
i18n={i18n}
id={`MessageRequests--profile-sharing--${conversationType}`}
components={{
firstName: (
<strong
key="name"
className="module-message-request-actions__message__name"
>
<ContactName
firstName={firstName}
title={title}
preferFirstName
/>
</strong>
),
learnMore: (
<a
href="https://support.signal.org/hc/articles/360007459591"
target="_blank"
rel="noreferrer"
className="module-message-request-actions__message__learn-more"
>
{i18n('MessageRequests--learn-more')}
</a>
),
}}
/>
{conversationType === 'direct' ? (
<Intl
i18n={i18n}
id="MessageRequests--profile-sharing--direct"
components={{ firstName: firstNameContact, learnMore }}
/>
) : (
<Intl
i18n={i18n}
id="MessageRequests--profile-sharing--group"
components={{ firstName: firstNameContact, learnMore }}
/>
)}
</p>
<div className="module-message-request-actions__buttons">
<Button

View file

@ -1251,7 +1251,10 @@ export class Message extends React.PureComponent<Props, State> {
}
if (giftBadge.state === GiftBadgeStates.Unopened) {
const description = i18n(`icu:message--donation--unopened--${direction}`);
const description =
direction === 'incoming'
? i18n('icu:message--donation--unopened--incoming')
: i18n('icu:message--donation--unopened--outgoing');
const isRTL = getDirection(description) === 'rtl';
const { metadataWidth } = this.state;
@ -1931,26 +1934,23 @@ export class Message extends React.PureComponent<Props, State> {
isTapToViewError,
} = this.props;
const incomingString = isTapToViewExpired
? i18n('Message--tap-to-view-expired')
: i18n(
`Message--tap-to-view--incoming${
isVideo(attachments) ? '-video' : ''
}`
);
const outgoingString = i18n('Message--tap-to-view--outgoing');
const isDownloadPending = this.isAttachmentPending();
if (isDownloadPending) {
return;
}
// eslint-disable-next-line no-nested-ternary
return isTapToViewError
? i18n('incomingError')
: direction === 'outgoing'
? outgoingString
: incomingString;
if (isTapToViewError) {
return i18n('incomingError');
}
if (direction === 'outgoing') {
return i18n('Message--tap-to-view--outgoing');
}
if (isTapToViewExpired) {
return i18n('Message--tap-to-view-expired');
}
if (isVideo(attachments)) {
return i18n('Message--tap-to-view--incoming-video');
}
return i18n('Message--tap-to-view--incoming');
}
public renderTapToView(): JSX.Element {

View file

@ -26,6 +26,7 @@ import * as log from '../../logging/log';
import { formatDateTimeLong } from '../../util/timestamp';
import { DurationInSeconds } from '../../util/durations';
import { format as formatRelativeTime } from '../../util/expirationTimer';
import { missingCaseError } from '../../util';
export type Contact = Pick<
ConversationType,
@ -200,24 +201,49 @@ export class MessageDetail extends React.Component<Props> {
);
}
private renderContactGroupHeaderText(
sendStatus: undefined | SendStatus
): string {
const { i18n } = this.props;
if (sendStatus === undefined) {
return i18n('from');
}
switch (sendStatus) {
case SendStatus.Failed:
return i18n('MessageDetailsHeader--Failed');
case SendStatus.Pending:
return i18n('MessageDetailsHeader--Pending');
case SendStatus.Sent:
return i18n('MessageDetailsHeader--Sent');
case SendStatus.Delivered:
return i18n('MessageDetailsHeader--Delivered');
case SendStatus.Read:
return i18n('MessageDetailsHeader--Read');
case SendStatus.Viewed:
return i18n('MessageDetailsHeader--Viewed');
default:
throw missingCaseError(sendStatus);
}
}
private renderContactGroup(
sendStatus: undefined | SendStatus,
contacts: undefined | ReadonlyArray<Contact>
): ReactNode {
const { i18n } = this.props;
if (!contacts || !contacts.length) {
return null;
}
const i18nKey =
sendStatus === undefined ? 'from' : `MessageDetailsHeader--${sendStatus}`;
const sortedContacts = [...contacts].sort((a, b) =>
contactSortCollator.compare(a.title, b.title)
);
const headerText = this.renderContactGroupHeaderText(sendStatus);
return (
<div key={i18nKey} className="module-message-detail__contact-group">
<div key={headerText} className="module-message-detail__contact-group">
<div
className={classNames(
'module-message-detail__contact-group__header',
@ -225,8 +251,7 @@ export class MessageDetail extends React.Component<Props> {
`module-message-detail__contact-group__header--${sendStatus}`
)}
>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
{i18n(i18nKey)}
{headerText}
</div>
{sortedContacts.map(contact => this.renderContact(contact))}
</div>

View file

@ -35,6 +35,15 @@ export function MessageRequestActions({
}: Props): JSX.Element {
const [mrState, setMrState] = React.useState(MessageRequestState.default);
const name = (
<strong
key="name"
className="module-message-request-actions__message__name"
>
<ContactName firstName={firstName} title={title} preferFirstName />
</strong>
);
return (
<>
{mrState !== MessageRequestState.default ? (
@ -53,26 +62,34 @@ export function MessageRequestActions({
) : null}
<div className="module-message-request-actions">
<p className="module-message-request-actions__message">
<Intl
i18n={i18n}
id={`MessageRequests--message-${conversationType}${
isBlocked ? '-blocked' : ''
}`}
components={{
name: (
<strong
key="name"
className="module-message-request-actions__message__name"
>
<ContactName
firstName={firstName}
title={title}
preferFirstName
/>
</strong>
),
}}
/>
{conversationType === 'direct' && isBlocked && (
<Intl
i18n={i18n}
id="MessageRequests--message-direct-blocked"
components={{ name }}
/>
)}
{conversationType === 'direct' && !isBlocked && (
<Intl
i18n={i18n}
id="MessageRequests--message-direct"
components={{ name }}
/>
)}
{conversationType === 'group' && isBlocked && (
<Intl
i18n={i18n}
id="MessageRequests--message-group-blocked"
components={{ name }}
/>
)}
{conversationType === 'group' && !isBlocked && (
<Intl
i18n={i18n}
id="MessageRequests--message-group"
components={{ name }}
/>
)}
</p>
<div className="module-message-request-actions__buttons">
<Button

View file

@ -132,13 +132,23 @@ export function MessageRequestActionsConfirmation({
onChangeState(MessageRequestState.default);
}}
title={
<Intl
i18n={i18n}
id={`MessageRequests--delete-${conversationType}-confirm-title`}
components={{
title: <ContactName key="name" title={title} />,
}}
/>
conversationType === 'direct' ? (
<Intl
i18n={i18n}
id="MessageRequests--delete-direct-confirm-title"
components={{
title: <ContactName key="name" title={title} />,
}}
/>
) : (
<Intl
i18n={i18n}
id="MessageRequests--delete-group-confirm-title"
components={{
title: <ContactName key="name" title={title} />,
}}
/>
)
}
actions={[
{

View file

@ -35,32 +35,30 @@ export function SafetyNumberNotification({
i18n,
toggleSafetyNumberModal,
}: Props): JSX.Element {
const changeKey = isGroup
? 'safetyNumberChangedGroup'
: 'safetyNumberChanged';
const name = (
<span
key="external-1"
className="module-safety-number-notification__contact"
>
<ContactName
title={contact.title}
module="module-safety-number-notification__contact"
/>
</span>
);
return (
<SystemMessage
icon="safety-number"
contents={
// eslint-disable-next-line local-rules/valid-i18n-keys
<Intl
id={changeKey}
components={{
name: (
<span
key="external-1"
className="module-safety-number-notification__contact"
>
<ContactName
title={contact.title}
module="module-safety-number-notification__contact"
/>
</span>
),
}}
i18n={i18n}
/>
isGroup ? (
<Intl
id="safetyNumberChangedGroup"
components={{ name }}
i18n={i18n}
/>
) : (
<Intl id="safetyNumberChanged" components={{ name }} i18n={i18n} />
)
}
button={
<Button

View file

@ -41,51 +41,46 @@ export type Props = PropsData & PropsHousekeeping;
export function TimerNotification(props: Props): JSX.Element {
const { disabled, i18n, title, type } = props;
let changeKey: string;
let timespan: string;
if (props.disabled) {
changeKey = 'disabledDisappearingMessages';
timespan = ''; // Set to the empty string to satisfy types
} else {
changeKey = 'theyChangedTheTimer';
timespan = expirationTimer.format(i18n, props.expireTimer);
}
const name = <ContactName key="external-1" title={title} />;
let message: ReactNode;
switch (type) {
case 'fromOther':
message = (
// eslint-disable-next-line local-rules/valid-i18n-keys
message = props.disabled ? (
<Intl
i18n={i18n}
id={changeKey}
components={{
name: <ContactName key="external-1" title={title} />,
time: timespan,
}}
id="disabledDisappearingMessages"
components={{ name }}
/>
) : (
<Intl
i18n={i18n}
id="theyChangedTheTimer"
components={{ name, time: timespan }}
/>
);
break;
case 'fromMe':
message = disabled
? i18n('youDisabledDisappearingMessages')
: i18n('youChangedTheTimer', {
time: timespan,
});
: i18n('youChangedTheTimer', { time: timespan });
break;
case 'fromSync':
message = disabled
? i18n('disappearingMessagesDisabled')
: i18n('timerSetOnSync', {
time: timespan,
});
: i18n('timerSetOnSync', { time: timespan });
break;
case 'fromMember':
message = disabled
? i18n('disappearingMessagesDisabledByMember')
: i18n('timerSetByMember', {
time: timespan,
});
: i18n('timerSetByMember', { time: timespan });
break;
default:
log.warn('TimerNotification: unsupported type provided:', type);

View file

@ -30,42 +30,64 @@ type PropsHousekeeping = {
export type Props = PropsData & PropsHousekeeping;
function UnsupportedMessageContents({ canProcessNow, contact, i18n }: Props) {
const { isMe } = contact;
const contactName = (
<span key="external-1" className="module-unsupported-message__contact">
<ContactName
title={contact.title}
module="module-unsupported-message__contact"
/>
</span>
);
if (isMe) {
if (canProcessNow) {
return (
<Intl
id="Message--unsupported-message-ask-to-resend"
components={{ contact: contactName }}
i18n={i18n}
/>
);
}
return (
<Intl
id="Message--from-me-unsupported-message"
components={{ contact: contactName }}
i18n={i18n}
/>
);
}
if (canProcessNow) {
return (
<Intl
id="Message--from-me-unsupported-message-ask-to-resend"
components={{ contact: contactName }}
i18n={i18n}
/>
);
}
return (
<Intl
id="Message--from-me-unsupported-message"
components={{ contact: contactName }}
i18n={i18n}
/>
);
}
export function UnsupportedMessage({
canProcessNow,
contact,
i18n,
}: Props): JSX.Element {
const { isMe } = contact;
const otherStringId = canProcessNow
? 'Message--unsupported-message-ask-to-resend'
: 'Message--unsupported-message';
const meStringId = canProcessNow
? 'Message--from-me-unsupported-message-ask-to-resend'
: 'Message--from-me-unsupported-message';
const stringId = isMe ? meStringId : otherStringId;
const icon = canProcessNow ? 'unsupported--can-process' : 'unsupported';
return (
<SystemMessage
icon={icon}
icon={canProcessNow ? 'unsupported--can-process' : 'unsupported'}
contents={
// eslint-disable-next-line local-rules/valid-i18n-keys
<Intl
id={stringId}
components={{
contact: (
<span
key="external-1"
className="module-unsupported-message__contact"
>
<ContactName
title={contact.title}
module="module-unsupported-message__contact"
/>
</span>
),
}}
<UnsupportedMessageContents
canProcessNow={canProcessNow}
contact={contact}
i18n={i18n}
/>
}

View file

@ -25,45 +25,43 @@ type PropsHousekeeping = {
export type Props = PropsData & PropsHousekeeping;
export class VerificationNotification extends React.Component<Props> {
public getStringId(): string {
const { isLocal, type } = this.props;
public renderContents(): JSX.Element {
const { contact, isLocal, type, i18n } = this.props;
const name = (
<ContactName
key="external-1"
title={contact.title}
module="module-verification-notification__contact"
/>
);
switch (type) {
case 'markVerified':
return isLocal
? 'youMarkedAsVerified'
: 'youMarkedAsVerifiedOtherDevice';
return isLocal ? (
<Intl id="youMarkedAsVerified" components={{ name }} i18n={i18n} />
) : (
<Intl
id="youMarkedAsVerifiedOtherDevice"
components={{ name }}
i18n={i18n}
/>
);
case 'markNotVerified':
return isLocal
? 'youMarkedAsNotVerified'
: 'youMarkedAsNotVerifiedOtherDevice';
return isLocal ? (
<Intl id="youMarkedAsNotVerified" components={{ name }} i18n={i18n} />
) : (
<Intl
id="youMarkedAsNotVerifiedOtherDevice"
components={{ name }}
i18n={i18n}
/>
);
default:
throw missingCaseError(type);
}
}
public renderContents(): JSX.Element {
const { contact, i18n } = this.props;
const id = this.getStringId();
return (
// eslint-disable-next-line local-rules/valid-i18n-keys
<Intl
id={id}
components={{
name: (
<ContactName
key="external-1"
title={contact.title}
module="module-verification-notification__contact"
/>
),
}}
i18n={i18n}
/>
);
}
public override render(): JSX.Element {
const { type } = this.props;
const icon = type === 'markVerified' ? 'verified' : 'verified-not';

View file

@ -1,8 +1,6 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable local-rules/valid-i18n-keys */
import React, {
useEffect,
useMemo,
@ -212,7 +210,8 @@ export function ChooseGroupMembersModal({
if (virtualIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'contactsHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('contactsHeader'),
};
}
@ -250,7 +249,8 @@ export function ChooseGroupMembersModal({
if (virtualIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
};
}
if (virtualIndex === 1) {
@ -268,7 +268,8 @@ export function ChooseGroupMembersModal({
if (virtualIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
// eslint-disable-next-line @typescript-eslint/no-shadow
getHeaderText: i18n => i18n('findByUsernameHeader'),
};
}
if (virtualIndex === 1) {
@ -307,16 +308,18 @@ export function ChooseGroupMembersModal({
let item;
switch (row?.type) {
case RowType.Header:
case RowType.Header: {
const headerText = row.getHeaderText(i18n);
item = (
<div
className="module-conversation-list__item--header"
aria-label={i18n(row.i18nKey)}
aria-label={headerText}
>
{i18n(row.i18nKey)}
{headerText}
</div>
);
break;
}
case RowType.ContactCheckbox:
item = (
<ContactCheckbox

View file

@ -618,8 +618,7 @@ function ConversationDetailsCallButton({
onClick={onClick}
variant={ButtonVariant.Details}
>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */}
{i18n(type)}
{type === 'audio' ? i18n('audio') : i18n('video')}
</Button>
);

View file

@ -71,11 +71,25 @@ function MediaSection({
const first = section.mediaItems[0];
const { message } = first;
const date = moment(getMessageTimestamp(message));
const header =
section.type === 'yearMonth'
? date.format(MONTH_FORMAT)
: // eslint-disable-next-line local-rules/valid-i18n-keys
i18n(section.type);
function getHeader(): string {
switch (section.type) {
case 'yearMonth':
return date.format(MONTH_FORMAT);
case 'today':
return i18n('today');
case 'yesterday':
return i18n('yesterday');
case 'thisWeek':
return i18n('thisWeek');
case 'thisMonth':
return i18n('thisMonth');
default:
throw missingCaseError(section);
}
}
const header = getHeader();
return (
<AttachmentSection

View file

@ -24,6 +24,7 @@ import { Emoji } from './Emoji';
import { dataByCategory, search } from './lib';
import type { LocalizerType } from '../../types/Util';
import { isSingleGrapheme } from '../../util/grapheme';
import { missingCaseError } from '../../util';
export type EmojiPickDataType = {
skinTone?: number;
@ -61,7 +62,9 @@ const categories = [
'object',
'symbol',
'flag',
];
] as const;
type Category = typeof categories[number];
export const EmojiPicker = React.memo(
React.forwardRef<HTMLDivElement, Props>(
@ -80,7 +83,7 @@ export const EmojiPicker = React.memo(
ref
) => {
const [firstRecent] = React.useState(recentEmojis);
const [selectedCategory, setSelectedCategory] = React.useState(
const [selectedCategory, setSelectedCategory] = React.useState<Category>(
categories[0]
);
const [searchMode, setSearchMode] = React.useState(false);
@ -277,7 +280,7 @@ export const EmojiPicker = React.memo(
const { category } = e.currentTarget.dataset;
if (category) {
setSelectedCategory(category);
setSelectedCategory(category as Category);
setScrollToRow(catToRowOffsets[category]);
}
},
@ -332,11 +335,36 @@ export const EmojiPicker = React.memo(
findLast(catOffsetEntries, ([, row]) => rowStartIndex >= row) ||
categories;
setSelectedCategory(cat);
setSelectedCategory(cat as Category);
}, 10),
[catOffsetEntries]
);
function getCategoryButtonLabel(category: Category): string {
switch (category) {
case 'recents':
return i18n('EmojiPicker__button--recents');
case 'emoji':
return i18n('EmojiPicker__button--emoji');
case 'animal':
return i18n('EmojiPicker__button--animal');
case 'food':
return i18n('EmojiPicker__button--food');
case 'activity':
return i18n('EmojiPicker__button--activity');
case 'travel':
return i18n('EmojiPicker__button--travel');
case 'object':
return i18n('EmojiPicker__button--object');
case 'symbol':
return i18n('EmojiPicker__button--symbol');
case 'flag':
return i18n('EmojiPicker__button--flag');
default:
throw missingCaseError(category);
}
}
return (
<FocusTrap
focusTrapOptions={{
@ -394,7 +422,7 @@ export const EmojiPicker = React.memo(
? 'module-emoji-picker__button--selected'
: null
)}
aria-label={i18n(`EmojiPicker__button--${cat}`)}
aria-label={getCategoryButtonLabel(cat)}
/>
)
)

View file

@ -314,7 +314,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'contactsHeader',
getHeaderText: i18n => i18n('contactsHeader'),
};
}
@ -342,7 +342,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
};
}
if (virtualRowIndex === 1) {
@ -363,7 +363,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
getHeaderText: i18n => i18n('findByUsernameHeader'),
};
}
if (virtualRowIndex === 1) {

View file

@ -194,7 +194,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'contactsHeader',
getHeaderText: i18n => i18n('contactsHeader'),
};
}
@ -215,7 +215,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'groupsHeader',
getHeaderText: i18n => i18n('groupsHeader'),
};
}
@ -236,7 +236,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
getHeaderText: i18n => i18n('findByUsernameHeader'),
};
}
@ -258,7 +258,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
};
}

View file

@ -150,12 +150,12 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
case 0:
return {
type: RowType.Header,
i18nKey: 'LeftPane--pinned',
getHeaderText: i18n => i18n('LeftPane--pinned'),
};
case pinnedConversations.length + 1:
return {
type: RowType.Header,
i18nKey: 'LeftPane--chats',
getHeaderText: i18n => i18n('LeftPane--chats'),
};
case pinnedConversations.length + conversations.length + 2:
if (archivedConversationsCount) {

View file

@ -229,7 +229,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
if (rowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'conversationsHeader',
getHeaderText: i18n => i18n('conversationsHeader'),
};
}
assertDev(
@ -250,7 +250,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
if (localIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'contactsHeader',
getHeaderText: i18n => i18n('contactsHeader'),
};
}
assertDev(
@ -274,7 +274,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
if (localIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'messagesHeader',
getHeaderText: i18n => i18n('messagesHeader'),
};
}
assertDev(

View file

@ -258,7 +258,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
if (rowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'setGroupMetadata__members-header',
getHeaderText: i18n => i18n('setGroupMetadata__members-header'),
};
}

View file

@ -67,7 +67,18 @@ export function renderChangeDetail<T>(
detail: GroupV2ChangeDetailType,
options: RenderOptionsType<T>
): T | string | ReadonlyArray<T | string> {
const { from, i18n, ourACI, ourPNI, renderContact, renderString } = options;
const {
from,
i18n: localizer,
ourACI,
ourPNI,
renderContact,
renderString,
} = options;
function i18n(id: string, components?: ReplacementValuesType<T | string>) {
return renderString(id, localizer, components);
}
const isOurUuid = (uuid?: UUIDStringType): boolean => {
if (!uuid) {
@ -79,88 +90,88 @@ export function renderChangeDetail<T>(
if (detail.type === 'create') {
if (fromYou) {
return renderString('GroupV2--create--you', i18n);
return i18n('GroupV2--create--you');
}
if (from) {
return renderString('GroupV2--create--other', i18n, {
return i18n('GroupV2--create--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--create--unknown', i18n);
return i18n('GroupV2--create--unknown');
}
if (detail.type === 'title') {
const { newTitle } = detail;
if (newTitle) {
if (fromYou) {
return renderString('GroupV2--title--change--you', i18n, { newTitle });
return i18n('GroupV2--title--change--you', { newTitle });
}
if (from) {
return renderString('GroupV2--title--change--other', i18n, {
return i18n('GroupV2--title--change--other', {
memberName: renderContact(from),
newTitle,
});
}
return renderString('GroupV2--title--change--unknown', i18n, {
return i18n('GroupV2--title--change--unknown', {
newTitle,
});
}
if (fromYou) {
return renderString('GroupV2--title--remove--you', i18n);
return i18n('GroupV2--title--remove--you');
}
if (from) {
return renderString('GroupV2--title--remove--other', i18n, {
return i18n('GroupV2--title--remove--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--title--remove--unknown', i18n);
return i18n('GroupV2--title--remove--unknown');
}
if (detail.type === 'avatar') {
if (detail.removed) {
if (fromYou) {
return renderString('GroupV2--avatar--remove--you', i18n);
return i18n('GroupV2--avatar--remove--you');
}
if (from) {
return renderString('GroupV2--avatar--remove--other', i18n, {
return i18n('GroupV2--avatar--remove--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--avatar--remove--unknown', i18n);
return i18n('GroupV2--avatar--remove--unknown');
}
if (fromYou) {
return renderString('GroupV2--avatar--change--you', i18n);
return i18n('GroupV2--avatar--change--you');
}
if (from) {
return renderString('GroupV2--avatar--change--other', i18n, {
return i18n('GroupV2--avatar--change--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--avatar--change--unknown', i18n);
return i18n('GroupV2--avatar--change--unknown');
}
if (detail.type === 'access-attributes') {
const { newPrivilege } = detail;
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-attributes--admins--you', i18n);
return i18n('GroupV2--access-attributes--admins--you');
}
if (from) {
return renderString('GroupV2--access-attributes--admins--other', i18n, {
return i18n('GroupV2--access-attributes--admins--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--access-attributes--admins--unknown', i18n);
return i18n('GroupV2--access-attributes--admins--unknown');
}
if (newPrivilege === AccessControlEnum.MEMBER) {
if (fromYou) {
return renderString('GroupV2--access-attributes--all--you', i18n);
return i18n('GroupV2--access-attributes--all--you');
}
if (from) {
return renderString('GroupV2--access-attributes--all--other', i18n, {
return i18n('GroupV2--access-attributes--all--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--access-attributes--all--unknown', i18n);
return i18n('GroupV2--access-attributes--all--unknown');
}
log.warn(
`access-attributes change type, privilege ${newPrivilege} is unknown`
@ -172,25 +183,25 @@ export function renderChangeDetail<T>(
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-members--admins--you', i18n);
return i18n('GroupV2--access-members--admins--you');
}
if (from) {
return renderString('GroupV2--access-members--admins--other', i18n, {
return i18n('GroupV2--access-members--admins--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--access-members--admins--unknown', i18n);
return i18n('GroupV2--access-members--admins--unknown');
}
if (newPrivilege === AccessControlEnum.MEMBER) {
if (fromYou) {
return renderString('GroupV2--access-members--all--you', i18n);
return i18n('GroupV2--access-members--all--you');
}
if (from) {
return renderString('GroupV2--access-members--all--other', i18n, {
return i18n('GroupV2--access-members--all--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--access-members--all--unknown', i18n);
return i18n('GroupV2--access-members--all--unknown');
}
log.warn(
`access-members change type, privilege ${newPrivilege} is unknown`
@ -202,35 +213,29 @@ export function renderChangeDetail<T>(
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-invite-link--enabled--you', i18n);
return i18n('GroupV2--access-invite-link--enabled--you');
}
if (from) {
return renderString(
return i18n(
'GroupV2--access-invite-link--enabled--other',
i18n,
{ adminName: renderContact(from) }
);
}
return renderString(
'GroupV2--access-invite-link--enabled--unknown',
i18n
);
return i18n('GroupV2--access-invite-link--enabled--unknown');
}
if (newPrivilege === AccessControlEnum.ANY) {
if (fromYou) {
return renderString('GroupV2--access-invite-link--disabled--you', i18n);
return i18n('GroupV2--access-invite-link--disabled--you');
}
if (from) {
return renderString(
return i18n(
'GroupV2--access-invite-link--disabled--other',
i18n,
{ adminName: renderContact(from) }
);
}
return renderString(
'GroupV2--access-invite-link--disabled--unknown',
i18n
);
return i18n('GroupV2--access-invite-link--disabled--unknown');
}
log.warn(
`access-invite-link change type, privilege ${newPrivilege} is unknown`
@ -243,27 +248,27 @@ export function renderChangeDetail<T>(
if (weAreJoiner) {
if (fromYou) {
return renderString('GroupV2--member-add--you--you', i18n);
return i18n('GroupV2--member-add--you--you');
}
if (from) {
return renderString('GroupV2--member-add--you--other', i18n, {
return i18n('GroupV2--member-add--you--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--member-add--you--unknown', i18n);
return i18n('GroupV2--member-add--you--unknown');
}
if (fromYou) {
return renderString('GroupV2--member-add--other--you', i18n, {
return i18n('GroupV2--member-add--other--you', {
memberName: renderContact(uuid),
});
}
if (from) {
return renderString('GroupV2--member-add--other--other', i18n, {
return i18n('GroupV2--member-add--other--other', {
adderName: renderContact(from),
addeeName: renderContact(uuid),
});
}
return renderString('GroupV2--member-add--other--unknown', i18n, {
return i18n('GroupV2--member-add--other--unknown', {
memberName: renderContact(uuid),
});
}
@ -276,54 +281,51 @@ export function renderChangeDetail<T>(
if (weAreJoiner) {
// They can't be the same, no fromYou check here
if (from) {
return renderString('GroupV2--member-add--you--other', i18n, {
return i18n('GroupV2--member-add--you--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--member-add--you--unknown', i18n);
return i18n('GroupV2--member-add--you--unknown');
}
if (fromYou) {
return renderString('GroupV2--member-add--invited--you', i18n, {
return i18n('GroupV2--member-add--invited--you', {
inviteeName: renderContact(uuid),
});
}
if (from) {
return renderString('GroupV2--member-add--invited--other', i18n, {
return i18n('GroupV2--member-add--invited--other', {
memberName: renderContact(from),
inviteeName: renderContact(uuid),
});
}
return renderString('GroupV2--member-add--invited--unknown', i18n, {
return i18n('GroupV2--member-add--invited--unknown', {
inviteeName: renderContact(uuid),
});
}
if (weAreJoiner) {
if (inviter) {
return renderString('GroupV2--member-add--from-invite--you', i18n, {
return i18n('GroupV2--member-add--from-invite--you', {
inviterName: renderContact(inviter),
});
}
return renderString(
'GroupV2--member-add--from-invite--you-no-from',
i18n
);
return i18n('GroupV2--member-add--from-invite--you-no-from');
}
if (weAreInviter) {
return renderString('GroupV2--member-add--from-invite--from-you', i18n, {
return i18n('GroupV2--member-add--from-invite--from-you', {
inviteeName: renderContact(uuid),
});
}
if (inviter) {
return renderString('GroupV2--member-add--from-invite--other', i18n, {
return i18n('GroupV2--member-add--from-invite--other', {
inviteeName: renderContact(uuid),
inviterName: renderContact(inviter),
});
}
return renderString(
return i18n(
'GroupV2--member-add--from-invite--other-no-from',
i18n,
{
inviteeName: renderContact(uuid),
}
@ -333,10 +335,10 @@ export function renderChangeDetail<T>(
const { uuid } = detail;
if (fromYou && isOurUuid(uuid)) {
return renderString('GroupV2--member-add-from-link--you--you', i18n);
return i18n('GroupV2--member-add-from-link--you--you');
}
if (from && uuid === from) {
return renderString('GroupV2--member-add-from-link--other', i18n, {
return i18n('GroupV2--member-add-from-link--other', {
memberName: renderContact(from),
});
}
@ -344,7 +346,7 @@ export function renderChangeDetail<T>(
// Note: this shouldn't happen, because we only capture 'add-from-link' status
// from group change events, which always have a sender.
log.warn('member-add-from-link change type; we have no from!');
return renderString('GroupV2--member-add--other--unknown', i18n, {
return i18n('GroupV2--member-add--other--unknown', {
memberName: renderContact(uuid),
});
}
@ -354,9 +356,9 @@ export function renderChangeDetail<T>(
if (weAreJoiner) {
if (from) {
return renderString(
return i18n(
'GroupV2--member-add-from-admin-approval--you--other',
i18n,
{ adminName: renderContact(from) }
);
}
@ -366,23 +368,20 @@ export function renderChangeDetail<T>(
log.warn(
'member-add-from-admin-approval change type; we have no from, and we are joiner!'
);
return renderString(
'GroupV2--member-add-from-admin-approval--you--unknown',
i18n
);
return i18n('GroupV2--member-add-from-admin-approval--you--unknown');
}
if (fromYou) {
return renderString(
return i18n(
'GroupV2--member-add-from-admin-approval--other--you',
i18n,
{ joinerName: renderContact(uuid) }
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--member-add-from-admin-approval--other--other',
i18n,
{
adminName: renderContact(from),
joinerName: renderContact(uuid),
@ -393,9 +392,9 @@ export function renderChangeDetail<T>(
// Note: this shouldn't happen, because we only capture 'add-from-admin-approval'
// status from group change events, which always have a sender.
log.warn('member-add-from-admin-approval change type; we have no from');
return renderString(
return i18n(
'GroupV2--member-add-from-admin-approval--other--unknown',
i18n,
{ joinerName: renderContact(uuid) }
);
}
@ -405,33 +404,33 @@ export function renderChangeDetail<T>(
if (weAreLeaver) {
if (fromYou) {
return renderString('GroupV2--member-remove--you--you', i18n);
return i18n('GroupV2--member-remove--you--you');
}
if (from) {
return renderString('GroupV2--member-remove--you--other', i18n, {
return i18n('GroupV2--member-remove--you--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--member-remove--you--unknown', i18n);
return i18n('GroupV2--member-remove--you--unknown');
}
if (fromYou) {
return renderString('GroupV2--member-remove--other--you', i18n, {
return i18n('GroupV2--member-remove--other--you', {
memberName: renderContact(uuid),
});
}
if (from && from === uuid) {
return renderString('GroupV2--member-remove--other--self', i18n, {
return i18n('GroupV2--member-remove--other--self', {
memberName: renderContact(from),
});
}
if (from) {
return renderString('GroupV2--member-remove--other--other', i18n, {
return i18n('GroupV2--member-remove--other--other', {
adminName: renderContact(from),
memberName: renderContact(uuid),
});
}
return renderString('GroupV2--member-remove--other--unknown', i18n, {
return i18n('GroupV2--member-remove--other--unknown', {
memberName: renderContact(uuid),
});
}
@ -442,77 +441,59 @@ export function renderChangeDetail<T>(
if (newPrivilege === RoleEnum.ADMINISTRATOR) {
if (weAreMember) {
if (from) {
return renderString(
return i18n(
'GroupV2--member-privilege--promote--you--other',
i18n,
{ adminName: renderContact(from) }
);
}
return renderString(
'GroupV2--member-privilege--promote--you--unknown',
i18n
);
return i18n('GroupV2--member-privilege--promote--you--unknown');
}
if (fromYou) {
return renderString(
'GroupV2--member-privilege--promote--other--you',
i18n,
{ memberName: renderContact(uuid) }
);
return i18n('GroupV2--member-privilege--promote--other--you', {
memberName: renderContact(uuid),
});
}
if (from) {
return renderString(
'GroupV2--member-privilege--promote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(uuid),
}
);
return i18n('GroupV2--member-privilege--promote--other--other', {
adminName: renderContact(from),
memberName: renderContact(uuid),
});
}
return renderString(
'GroupV2--member-privilege--promote--other--unknown',
i18n,
{ memberName: renderContact(uuid) }
);
return i18n('GroupV2--member-privilege--promote--other--unknown', {
memberName: renderContact(uuid),
});
}
if (newPrivilege === RoleEnum.DEFAULT) {
if (weAreMember) {
if (from) {
return renderString(
'GroupV2--member-privilege--demote--you--other',
i18n,
{ adminName: renderContact(from) }
);
return i18n('GroupV2--member-privilege--demote--you--other', {
adminName: renderContact(from),
});
}
return renderString(
'GroupV2--member-privilege--demote--you--unknown',
i18n
);
return i18n('GroupV2--member-privilege--demote--you--unknown');
}
if (fromYou) {
return renderString(
'GroupV2--member-privilege--demote--other--you',
i18n,
{ memberName: renderContact(uuid) }
);
return i18n('GroupV2--member-privilege--demote--other--you', {
memberName: renderContact(uuid),
});
}
if (from) {
return renderString(
return i18n(
'GroupV2--member-privilege--demote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(uuid),
}
);
}
return renderString(
return i18n(
'GroupV2--member-privilege--demote--other--unknown',
i18n,
{ memberName: renderContact(uuid) }
);
}
@ -526,39 +507,39 @@ export function renderChangeDetail<T>(
const weAreInvited = isOurUuid(uuid);
if (weAreInvited) {
if (from) {
return renderString('GroupV2--pending-add--one--you--other', i18n, {
return i18n('GroupV2--pending-add--one--you--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--pending-add--one--you--unknown', i18n);
return i18n('GroupV2--pending-add--one--you--unknown');
}
if (fromYou) {
return renderString('GroupV2--pending-add--one--other--you', i18n, {
return i18n('GroupV2--pending-add--one--other--you', {
inviteeName: renderContact(uuid),
});
}
if (from) {
return renderString('GroupV2--pending-add--one--other--other', i18n, {
return i18n('GroupV2--pending-add--one--other--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--pending-add--one--other--unknown', i18n);
return i18n('GroupV2--pending-add--one--other--unknown');
}
if (detail.type === 'pending-add-many') {
const { count } = detail;
if (fromYou) {
return renderString('GroupV2--pending-add--many--you', i18n, {
return i18n('GroupV2--pending-add--many--you', {
count: count.toString(),
});
}
if (from) {
return renderString('GroupV2--pending-add--many--other', i18n, {
return i18n('GroupV2--pending-add--many--other', {
memberName: renderContact(from),
count: count.toString(),
});
}
return renderString('GroupV2--pending-add--many--unknown', i18n, {
return i18n('GroupV2--pending-add--many--unknown', {
count: count.toString(),
});
}
@ -571,91 +552,91 @@ export function renderChangeDetail<T>(
if (weAreInviter) {
if (sentByInvited) {
return renderString('GroupV2--pending-remove--decline--you', i18n, {
return i18n('GroupV2--pending-remove--decline--you', {
inviteeName: renderContact(uuid),
});
}
if (fromYou) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--one--you',
i18n,
{ inviteeName: renderContact(uuid) }
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--one--other',
i18n,
{
adminName: renderContact(from),
inviteeName: renderContact(uuid),
}
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--one--unknown',
i18n,
{ inviteeName: renderContact(uuid) }
);
}
if (sentByInvited) {
if (fromYou) {
return renderString('GroupV2--pending-remove--decline--from-you', i18n);
return i18n('GroupV2--pending-remove--decline--from-you');
}
if (inviter) {
return renderString('GroupV2--pending-remove--decline--other', i18n, {
return i18n('GroupV2--pending-remove--decline--other', {
memberName: renderContact(inviter),
});
}
return renderString('GroupV2--pending-remove--decline--unknown', i18n);
return i18n('GroupV2--pending-remove--decline--unknown');
}
if (inviter && sentByInviter) {
if (weAreInvited) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-own--to-you',
i18n,
{ inviterName: renderContact(inviter) }
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-own--unknown',
i18n,
{ inviterName: renderContact(inviter) }
);
}
if (inviter) {
if (fromYou) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--one--you',
i18n,
{ memberName: renderContact(inviter) }
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--one--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(inviter),
}
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--one--unknown',
i18n,
{ memberName: renderContact(inviter) }
);
}
if (fromYou) {
return renderString('GroupV2--pending-remove--revoke--one--you', i18n);
return i18n('GroupV2--pending-remove--revoke--one--you');
}
if (from) {
return renderString('GroupV2--pending-remove--revoke--one--other', i18n, {
return i18n('GroupV2--pending-remove--revoke--one--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--pending-remove--revoke--one--unknown', i18n);
return i18n('GroupV2--pending-remove--revoke--one--unknown');
}
if (detail.type === 'pending-remove-many') {
const { count, inviter } = detail;
@ -663,33 +644,33 @@ export function renderChangeDetail<T>(
if (weAreInviter) {
if (fromYou) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--many--you',
i18n,
{ count: count.toString() }
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--many--other',
i18n,
{
adminName: renderContact(from),
count: count.toString(),
}
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from-you--many--unknown',
i18n,
{ count: count.toString() }
);
}
if (inviter) {
if (fromYou) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--many--you',
i18n,
{
count: count.toString(),
memberName: renderContact(inviter),
@ -697,9 +678,9 @@ export function renderChangeDetail<T>(
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--many--other',
i18n,
{
adminName: renderContact(from),
count: count.toString(),
@ -707,9 +688,9 @@ export function renderChangeDetail<T>(
}
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke-invite-from--many--unknown',
i18n,
{
count: count.toString(),
memberName: renderContact(inviter),
@ -717,23 +698,23 @@ export function renderChangeDetail<T>(
);
}
if (fromYou) {
return renderString('GroupV2--pending-remove--revoke--many--you', i18n, {
return i18n('GroupV2--pending-remove--revoke--many--you', {
count: count.toString(),
});
}
if (from) {
return renderString(
return i18n(
'GroupV2--pending-remove--revoke--many--other',
i18n,
{
memberName: renderContact(from),
count: count.toString(),
}
);
}
return renderString(
return i18n(
'GroupV2--pending-remove--revoke--many--unknown',
i18n,
{ count: count.toString() }
);
}
@ -742,9 +723,9 @@ export function renderChangeDetail<T>(
const weAreJoiner = isOurUuid(uuid);
if (weAreJoiner) {
return renderString('GroupV2--admin-approval-add-one--you', i18n);
return i18n('GroupV2--admin-approval-add-one--you');
}
return renderString('GroupV2--admin-approval-add-one--other', i18n, {
return i18n('GroupV2--admin-approval-add-one--other', {
joinerName: renderContact(uuid),
});
}
@ -754,35 +735,29 @@ export function renderChangeDetail<T>(
if (weAreJoiner) {
if (fromYou) {
return renderString(
'GroupV2--admin-approval-remove-one--you--you',
i18n
);
return i18n('GroupV2--admin-approval-remove-one--you--you');
}
return renderString(
'GroupV2--admin-approval-remove-one--you--unknown',
i18n
);
return i18n('GroupV2--admin-approval-remove-one--you--unknown');
}
if (fromYou) {
return renderString(
return i18n(
'GroupV2--admin-approval-remove-one--other--you',
i18n,
{ joinerName: renderContact(uuid) }
);
}
if (from && from === uuid) {
return renderString(
return i18n(
'GroupV2--admin-approval-remove-one--other--own',
i18n,
{ joinerName: renderContact(uuid) }
);
}
if (from) {
return renderString(
return i18n(
'GroupV2--admin-approval-remove-one--other--other',
i18n,
{
adminName: renderContact(from),
joinerName: renderContact(uuid),
@ -792,9 +767,9 @@ export function renderChangeDetail<T>(
// We default to the user canceling their request, because it is far more likely that
// if an admin does the denial, we'll get a change event from them.
return renderString(
return i18n(
'GroupV2--admin-approval-remove-one--other--own',
i18n,
{ joinerName: renderContact(uuid) }
);
}
@ -803,11 +778,11 @@ export function renderChangeDetail<T>(
let firstMessage: T | string;
if (times === 1) {
firstMessage = renderString('GroupV2--admin-approval-bounce--one', i18n, {
firstMessage = i18n('GroupV2--admin-approval-bounce--one', {
joinerName: renderContact(uuid),
});
} else {
firstMessage = renderString('GroupV2--admin-approval-bounce', i18n, {
firstMessage = i18n('GroupV2--admin-approval-bounce', {
joinerName: renderContact(uuid),
numberOfRequests: String(times),
});
@ -835,99 +810,99 @@ export function renderChangeDetail<T>(
if (privilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--group-link-add--enabled--you', i18n);
return i18n('GroupV2--group-link-add--enabled--you');
}
if (from) {
return renderString('GroupV2--group-link-add--enabled--other', i18n, {
return i18n('GroupV2--group-link-add--enabled--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--group-link-add--enabled--unknown', i18n);
return i18n('GroupV2--group-link-add--enabled--unknown');
}
if (privilege === AccessControlEnum.ANY) {
if (fromYou) {
return renderString('GroupV2--group-link-add--disabled--you', i18n);
return i18n('GroupV2--group-link-add--disabled--you');
}
if (from) {
return renderString('GroupV2--group-link-add--disabled--other', i18n, {
return i18n('GroupV2--group-link-add--disabled--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--group-link-add--disabled--unknown', i18n);
return i18n('GroupV2--group-link-add--disabled--unknown');
}
log.warn(`group-link-add change type, privilege ${privilege} is unknown`);
return '';
}
if (detail.type === 'group-link-reset') {
if (fromYou) {
return renderString('GroupV2--group-link-reset--you', i18n);
return i18n('GroupV2--group-link-reset--you');
}
if (from) {
return renderString('GroupV2--group-link-reset--other', i18n, {
return i18n('GroupV2--group-link-reset--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--group-link-reset--unknown', i18n);
return i18n('GroupV2--group-link-reset--unknown');
}
if (detail.type === 'group-link-remove') {
if (fromYou) {
return renderString('GroupV2--group-link-remove--you', i18n);
return i18n('GroupV2--group-link-remove--you');
}
if (from) {
return renderString('GroupV2--group-link-remove--other', i18n, {
return i18n('GroupV2--group-link-remove--other', {
adminName: renderContact(from),
});
}
return renderString('GroupV2--group-link-remove--unknown', i18n);
return i18n('GroupV2--group-link-remove--unknown');
}
if (detail.type === 'description') {
if (detail.removed) {
if (fromYou) {
return renderString('GroupV2--description--remove--you', i18n);
return i18n('GroupV2--description--remove--you');
}
if (from) {
return renderString('GroupV2--description--remove--other', i18n, {
return i18n('GroupV2--description--remove--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--description--remove--unknown', i18n);
return i18n('GroupV2--description--remove--unknown');
}
if (fromYou) {
return renderString('GroupV2--description--change--you', i18n);
return i18n('GroupV2--description--change--you');
}
if (from) {
return renderString('GroupV2--description--change--other', i18n, {
return i18n('GroupV2--description--change--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--description--change--unknown', i18n);
return i18n('GroupV2--description--change--unknown');
}
if (detail.type === 'announcements-only') {
if (detail.announcementsOnly) {
if (fromYou) {
return renderString('GroupV2--announcements--admin--you', i18n);
return i18n('GroupV2--announcements--admin--you');
}
if (from) {
return renderString('GroupV2--announcements--admin--other', i18n, {
return i18n('GroupV2--announcements--admin--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--announcements--admin--unknown', i18n);
return i18n('GroupV2--announcements--admin--unknown');
}
if (fromYou) {
return renderString('GroupV2--announcements--member--you', i18n);
return i18n('GroupV2--announcements--member--you');
}
if (from) {
return renderString('GroupV2--announcements--member--other', i18n, {
return i18n('GroupV2--announcements--member--other', {
memberName: renderContact(from),
});
}
return renderString('GroupV2--announcements--member--unknown', i18n);
return i18n('GroupV2--announcements--member--unknown');
}
if (detail.type === 'summary') {
return renderString('icu:GroupV2--summary', i18n);
return i18n('icu:GroupV2--summary');
}
throw missingCaseError(detail);

View file

@ -511,7 +511,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
key: string,
_i18n: unknown,
components: ReplacementValuesType<string> | undefined
) => window.i18n(key, components),
) => {
// eslint-disable-next-line local-rules/valid-i18n-keys
return window.i18n(key, components);
},
});
return { text: changes.map(({ text }) => text).join(' ') };

View file

@ -4,7 +4,7 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { times } from 'lodash';
import { RowType } from '../../../components/ConversationList';
import { RowType, _testHeaderText } from '../../../components/ConversationList';
import { ContactCheckboxDisabledReason } from '../../../components/conversationList/ContactCheckbox';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
@ -117,10 +117,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
selectedContacts: [candidateContacts[1]],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'contactsHeader');
assert.deepEqual(helper.getRow(1), {
type: RowType.ContactCheckbox,
contact: candidateContacts[0],
@ -167,10 +164,10 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
selectedContacts: [],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'findByPhoneNumberHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.PhoneNumberCheckbox,
phoneNumber: {
@ -192,10 +189,10 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
selectedContacts: [],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'findByUsernameHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.UsernameCheckbox,
username: 'signal',

View file

@ -3,7 +3,7 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { RowType } from '../../../components/ConversationList';
import { RowType, _testHeaderText } from '../../../components/ConversationList';
import { FindDirection } from '../../../components/leftPane/LeftPaneHelper';
import {
getDefaultConversation,
@ -223,10 +223,7 @@ describe('LeftPaneComposeHelper', () => {
assert.deepEqual(helper.getRow(0), {
type: RowType.CreateNewGroup,
});
assert.deepEqual(helper.getRow(1), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(1)), 'contactsHeader');
assert.deepEqual(helper.getRow(2), {
type: RowType.Contact,
contact: composeContacts[0],
@ -258,11 +255,7 @@ describe('LeftPaneComposeHelper', () => {
assert.deepEqual(helper.getRow(0), {
type: RowType.CreateNewGroup,
});
assert.deepEqual(helper.getRow(1), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(1)), 'contactsHeader');
assert.deepEqual(helper.getRow(2), {
type: RowType.Contact,
contact: composeContacts[0],
@ -271,10 +264,7 @@ describe('LeftPaneComposeHelper', () => {
type: RowType.Contact,
contact: composeContacts[1],
});
assert.deepEqual(helper.getRow(4), {
type: RowType.Header,
i18nKey: 'groupsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(4)), 'groupsHeader');
assert.deepEqual(helper.getRow(5), {
type: RowType.SelectSingleGroup,
group: composeGroups[0],
@ -333,10 +323,10 @@ describe('LeftPaneComposeHelper', () => {
uuidFetchState: {},
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'findByPhoneNumberHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.StartNewConversation,
phoneNumber: {
@ -363,10 +353,10 @@ describe('LeftPaneComposeHelper', () => {
},
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'findByUsernameHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.UsernameSearchResult,
username,
@ -389,10 +379,7 @@ describe('LeftPaneComposeHelper', () => {
uuidFetchState: {},
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'contactsHeader');
assert.deepEqual(helper.getRow(1), {
type: RowType.Contact,
contact: composeContacts[0],
@ -401,10 +388,10 @@ describe('LeftPaneComposeHelper', () => {
type: RowType.Contact,
contact: composeContacts[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(3)),
'findByPhoneNumberHeader'
);
assert.deepEqual(helper.getRow(4), {
type: RowType.StartNewConversation,
phoneNumber: {

View file

@ -3,7 +3,7 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { RowType } from '../../../components/ConversationList';
import { RowType, _testHeaderText } from '../../../components/ConversationList';
import { FindDirection } from '../../../components/leftPane/LeftPaneHelper';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
@ -339,10 +339,7 @@ describe('LeftPaneInboxHelper', () => {
pinnedConversations,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'LeftPane--pinned',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'LeftPane--pinned');
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: pinnedConversations[0],
@ -351,10 +348,7 @@ describe('LeftPaneInboxHelper', () => {
type: RowType.Conversation,
conversation: pinnedConversations[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'LeftPane--chats',
});
assert.deepEqual(_testHeaderText(helper.getRow(3)), 'LeftPane--chats');
assert.deepEqual(helper.getRow(4), {
type: RowType.Conversation,
conversation: conversations[0],
@ -388,10 +382,7 @@ describe('LeftPaneInboxHelper', () => {
archivedConversations: [getDefaultConversation()],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'LeftPane--pinned',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'LeftPane--pinned');
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: pinnedConversations[0],
@ -400,10 +391,7 @@ describe('LeftPaneInboxHelper', () => {
type: RowType.Conversation,
conversation: pinnedConversations[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'LeftPane--chats',
});
assert.deepEqual(_testHeaderText(helper.getRow(3)), 'LeftPane--chats');
assert.deepEqual(helper.getRow(4), {
type: RowType.Conversation,
conversation: conversations[0],

View file

@ -4,7 +4,7 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid';
import { RowType } from '../../../components/ConversationList';
import { RowType, _testHeaderText } from '../../../components/ConversationList';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
import { LeftPaneSearchHelper } from '../../../components/leftPane/LeftPaneSearchHelper';
@ -192,10 +192,10 @@ describe('LeftPaneSearchHelper', () => {
startSearchCounter: 0,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'conversationsHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'conversationsHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: conversations[0],
@ -204,18 +204,12 @@ describe('LeftPaneSearchHelper', () => {
type: RowType.Conversation,
conversation: conversations[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(3)), 'contactsHeader');
assert.deepEqual(helper.getRow(4), {
type: RowType.Conversation,
conversation: contacts[0],
});
assert.deepEqual(helper.getRow(5), {
type: RowType.Header,
i18nKey: 'messagesHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(5)), 'messagesHeader');
assert.deepEqual(helper.getRow(6), {
type: RowType.MessageSearchResult,
messageId: messages[0].id,
@ -244,18 +238,12 @@ describe('LeftPaneSearchHelper', () => {
startSearchCounter: 0,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'contactsHeader');
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: contacts[0],
});
assert.deepEqual(helper.getRow(2), {
type: RowType.Header,
i18nKey: 'messagesHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(2)), 'messagesHeader');
assert.deepEqual(helper.getRow(3), {
type: RowType.MessageSearchResult,
messageId: messages[0].id,
@ -287,10 +275,10 @@ describe('LeftPaneSearchHelper', () => {
startSearchCounter: 0,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'conversationsHeader',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'conversationsHeader'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: conversations[0],
@ -299,10 +287,7 @@ describe('LeftPaneSearchHelper', () => {
type: RowType.Conversation,
conversation: conversations[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'messagesHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(3)), 'messagesHeader');
assert.deepEqual(helper.getRow(4), {
type: RowType.MessageSearchResult,
messageId: messages[0].id,
@ -332,10 +317,7 @@ describe('LeftPaneSearchHelper', () => {
startSearchCounter: 0,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'conversationsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(0)), 'conversationsHeader');
assert.deepEqual(helper.getRow(1), {
type: RowType.Conversation,
conversation: conversations[0],
@ -344,10 +326,7 @@ describe('LeftPaneSearchHelper', () => {
type: RowType.Conversation,
conversation: conversations[1],
});
assert.deepEqual(helper.getRow(3), {
type: RowType.Header,
i18nKey: 'contactsHeader',
});
assert.deepEqual(_testHeaderText(helper.getRow(3)), 'contactsHeader');
assert.deepEqual(helper.getRow(4), {
type: RowType.Conversation,
conversation: contacts[0],

View file

@ -3,7 +3,7 @@
import { assert } from 'chai';
import * as sinon from 'sinon';
import { RowType } from '../../../components/ConversationList';
import { RowType, _testHeaderText } from '../../../components/ConversationList';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
import { DurationInSeconds } from '../../../util/durations';
@ -92,10 +92,10 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
selectedContacts,
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'setGroupMetadata__members-header',
});
assert.deepEqual(
_testHeaderText(helper.getRow(0)),
'setGroupMetadata__members-header'
);
assert.deepEqual(helper.getRow(1), {
type: RowType.Contact,
contact: selectedContacts[0],