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) { function isI18nCall(node) {
return ( return (
node.type === 'CallExpression' && (node.type === 'CallExpression' &&
node.callee.type === 'Identifier' && node.callee.type === 'Identifier' &&
node.callee.name === 'i18n' node.callee.name === 'i18n') ||
(node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'i18n')
); );
} }
@ -36,20 +38,7 @@ function valueToMessageKey(node) {
if (isStringLiteral(node)) { if (isStringLiteral(node)) {
return node.value; return node.value;
} }
return null;
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('(.*)')}$`);
} }
function getI18nCallMessageKey(node) { function getI18nCallMessageKey(node) {
@ -80,24 +69,11 @@ function getIntlElementMessageKey(node) {
let value = idAttribute.value; let value = idAttribute.value;
if (value.type === 'JSXExpressionContainer') {
value = value.expression;
}
return valueToMessageKey(value); return valueToMessageKey(value);
} }
function isValidMessageKey(key) { function isValidMessageKey(key) {
if (typeof key === 'string') { return Object.hasOwn(messages, key);
if (Object.hasOwn(messages, key)) {
return true;
}
} else if (key instanceof RegExp) {
if (messageKeys.some(k => key.test(k))) {
return true;
}
}
return false;
} }
module.exports = { module.exports = {

View file

@ -27,27 +27,68 @@ ruleTester.run('valid-i18n-keys', rule, {
options: [{ messagesCacheKey }], options: [{ messagesCacheKey }],
}, },
{ {
code: 'i18n(`AddCaptionModal__${title}`)', code: `window.i18n("AddCaptionModal__title")`,
options: [{ messagesCacheKey }], options: [{ messagesCacheKey }],
}, },
{ {
code: `let jsx = <Intl id="AddCaptionModal__title"/>`, code: `let jsx = <Intl id="AddCaptionModal__title"/>`,
options: [{ messagesCacheKey }], 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"}/>`, code: `let jsx = <Intl id={"AddCaptionModal__title"}/>`,
options: [{ messagesCacheKey }], 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`}/>', code: 'let jsx = <Intl id={`AddCaptionModal__title`}/>',
options: [{ messagesCacheKey }], 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}`}/>', code: 'let jsx = <Intl id={`AddCaptionModal__${title}`}/>',
options: [{ messagesCacheKey }], 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")`, code: `i18n("THIS_KEY_SHOULD_NEVER_EXIST")`,
options: [{ messagesCacheKey }], options: [{ messagesCacheKey }],

View file

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

View file

@ -103,9 +103,17 @@ type MessageRowType = {
type HeaderRowType = { type HeaderRowType = {
type: RowType.Header; 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 SearchResultsLoadingFakeHeaderType = {
type: RowType.SearchResultsLoadingFakeHeader; type: RowType.SearchResultsLoadingFakeHeader;
}; };
@ -375,18 +383,18 @@ export function ConversationList({
/> />
); );
break; break;
case RowType.Header: case RowType.Header: {
const headerText = row.getHeaderText(i18n);
result = ( result = (
<div <div
className="module-conversation-list__item--header" className="module-conversation-list__item--header"
// eslint-disable-next-line local-rules/valid-i18n-keys aria-label={headerText}
aria-label={i18n(row.i18nKey)}
> >
{/* eslint-disable-next-line local-rules/valid-i18n-keys */} {headerText}
{i18n(row.i18nKey)}
</div> </div>
); );
break; break;
}
case RowType.MessageSearchResult: case RowType.MessageSearchResult:
result = <>{renderMessageSearchResult?.(row.messageId)}</>; result = <>{renderMessageSearchResult?.(row.messageId)}</>;
break; break;

View file

@ -7,6 +7,7 @@ import type { ConversationType } from '../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { GroupDialog } from './GroupDialog'; import { GroupDialog } from './GroupDialog';
import { sortByTitle } from '../util/sortByTitle'; import { sortByTitle } from '../util/sortByTitle';
import { missingCaseError } from '../util';
export type DataPropsType = { export type DataPropsType = {
conversationId: string; conversationId: string;
@ -70,8 +71,6 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
const keepHistory = hasMigrated const keepHistory = hasMigrated
? i18n('GroupV1--Migration--info--keep-history') ? i18n('GroupV1--Migration--info--keep-history')
: i18n('GroupV1--Migration--migrate--keep-history'); : i18n('GroupV1--Migration--migrate--keep-history');
const migrationKey = hasMigrated ? 'after' : 'before';
const droppedMembersKey = `GroupV1--Migration--info--removed--${migrationKey}`;
let primaryButtonText: string; let primaryButtonText: string;
let onClickPrimaryButton: () => void; let onClickPrimaryButton: () => void;
@ -116,14 +115,16 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
getPreferredBadge, getPreferredBadge,
i18n, i18n,
members: invitedMembers, members: invitedMembers,
prefix: 'GroupV1--Migration--info--invited', hasMigrated,
kind: 'invited',
theme, theme,
})} })}
{renderMembers({ {renderMembers({
getPreferredBadge, getPreferredBadge,
i18n, i18n,
members: droppedMembers, members: droppedMembers,
prefix: droppedMembersKey, hasMigrated,
kind: 'dropped',
theme, theme,
})} })}
</> </>
@ -136,26 +137,49 @@ function renderMembers({
getPreferredBadge, getPreferredBadge,
i18n, i18n,
members, members,
prefix, hasMigrated,
kind,
theme, theme,
}: Readonly<{ }: Readonly<{
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType; i18n: LocalizerType;
members: Array<ConversationType>; members: Array<ConversationType>;
prefix: string; hasMigrated: boolean;
kind: 'invited' | 'dropped';
theme: ThemeType; theme: ThemeType;
}>): React.ReactNode { }>): React.ReactNode {
if (!members.length) { if (!members.length) {
return null; return null;
} }
const postfix = members.length === 1 ? '--one' : '--many'; let text: string;
const key = `${prefix}${postfix}`; 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 ( return (
<> <>
{/* eslint-disable-next-line local-rules/valid-i18n-keys */} <GroupDialog.Paragraph>{text}</GroupDialog.Paragraph>
<GroupDialog.Paragraph>{i18n(key)}</GroupDialog.Paragraph>
<GroupDialog.Contacts <GroupDialog.Contacts
contacts={sortByTitle(members)} contacts={sortByTitle(members)}
getPreferredBadge={getPreferredBadge} 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 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public override render() { 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) { if (!id) {
log.error('Error: Intl id prop not provided'); log.error('Error: Intl id prop not provided');
return null; return null;
} }
if (!i18n.isLegacyFormat(id)) { if (!localizer.isLegacyFormat(id)) {
strictAssert( strictAssert(
!Array.isArray(components), !Array.isArray(components),
`components cannot be an array for ICU message ${id}` `components cannot be an array for ICU message ${id}`
); );
const intl = i18n.getIntl(); const intl = localizer.getIntl();
return intl.formatMessage({ id }, components); return intl.formatMessage({ id }, components);
} }
// eslint-disable-next-line local-rules/valid-i18n-keys const text = localizer(id);
const text = i18n(id);
const results: Array< const results: Array<
string | JSX.Element | Array<string | JSX.Element> | null string | JSX.Element | Array<string | JSX.Element> | null
> = []; > = [];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,6 +40,26 @@ export function MandatoryProfileSharingActions({
}: Props): JSX.Element { }: Props): JSX.Element {
const [mrState, setMrState] = React.useState(MessageRequestState.default); 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 ( return (
<> <>
{mrState !== MessageRequestState.default ? ( {mrState !== MessageRequestState.default ? (
@ -62,34 +82,19 @@ export function MandatoryProfileSharingActions({
) : null} ) : null}
<div className="module-message-request-actions"> <div className="module-message-request-actions">
<p className="module-message-request-actions__message"> <p className="module-message-request-actions__message">
<Intl {conversationType === 'direct' ? (
i18n={i18n} <Intl
id={`MessageRequests--profile-sharing--${conversationType}`} i18n={i18n}
components={{ id="MessageRequests--profile-sharing--direct"
firstName: ( components={{ firstName: firstNameContact, learnMore }}
<strong />
key="name" ) : (
className="module-message-request-actions__message__name" <Intl
> i18n={i18n}
<ContactName id="MessageRequests--profile-sharing--group"
firstName={firstName} components={{ firstName: firstNameContact, learnMore }}
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>
),
}}
/>
</p> </p>
<div className="module-message-request-actions__buttons"> <div className="module-message-request-actions__buttons">
<Button <Button

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,42 +30,64 @@ type PropsHousekeeping = {
export type Props = PropsData & 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({ export function UnsupportedMessage({
canProcessNow, canProcessNow,
contact, contact,
i18n, i18n,
}: Props): JSX.Element { }: 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 ( return (
<SystemMessage <SystemMessage
icon={icon} icon={canProcessNow ? 'unsupported--can-process' : 'unsupported'}
contents={ contents={
// eslint-disable-next-line local-rules/valid-i18n-keys <UnsupportedMessageContents
<Intl canProcessNow={canProcessNow}
id={stringId} contact={contact}
components={{
contact: (
<span
key="external-1"
className="module-unsupported-message__contact"
>
<ContactName
title={contact.title}
module="module-unsupported-message__contact"
/>
</span>
),
}}
i18n={i18n} i18n={i18n}
/> />
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ import { Emoji } from './Emoji';
import { dataByCategory, search } from './lib'; import { dataByCategory, search } from './lib';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { isSingleGrapheme } from '../../util/grapheme'; import { isSingleGrapheme } from '../../util/grapheme';
import { missingCaseError } from '../../util';
export type EmojiPickDataType = { export type EmojiPickDataType = {
skinTone?: number; skinTone?: number;
@ -61,7 +62,9 @@ const categories = [
'object', 'object',
'symbol', 'symbol',
'flag', 'flag',
]; ] as const;
type Category = typeof categories[number];
export const EmojiPicker = React.memo( export const EmojiPicker = React.memo(
React.forwardRef<HTMLDivElement, Props>( React.forwardRef<HTMLDivElement, Props>(
@ -80,7 +83,7 @@ export const EmojiPicker = React.memo(
ref ref
) => { ) => {
const [firstRecent] = React.useState(recentEmojis); const [firstRecent] = React.useState(recentEmojis);
const [selectedCategory, setSelectedCategory] = React.useState( const [selectedCategory, setSelectedCategory] = React.useState<Category>(
categories[0] categories[0]
); );
const [searchMode, setSearchMode] = React.useState(false); const [searchMode, setSearchMode] = React.useState(false);
@ -277,7 +280,7 @@ export const EmojiPicker = React.memo(
const { category } = e.currentTarget.dataset; const { category } = e.currentTarget.dataset;
if (category) { if (category) {
setSelectedCategory(category); setSelectedCategory(category as Category);
setScrollToRow(catToRowOffsets[category]); setScrollToRow(catToRowOffsets[category]);
} }
}, },
@ -332,11 +335,36 @@ export const EmojiPicker = React.memo(
findLast(catOffsetEntries, ([, row]) => rowStartIndex >= row) || findLast(catOffsetEntries, ([, row]) => rowStartIndex >= row) ||
categories; categories;
setSelectedCategory(cat); setSelectedCategory(cat as Category);
}, 10), }, 10),
[catOffsetEntries] [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 ( return (
<FocusTrap <FocusTrap
focusTrapOptions={{ focusTrapOptions={{
@ -394,7 +422,7 @@ export const EmojiPicker = React.memo(
? 'module-emoji-picker__button--selected' ? 'module-emoji-picker__button--selected'
: null : 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) { if (virtualRowIndex === 0) {
return { return {
type: RowType.Header, type: RowType.Header,
i18nKey: 'contactsHeader', getHeaderText: i18n => i18n('contactsHeader'),
}; };
} }
@ -342,7 +342,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 0) { if (virtualRowIndex === 0) {
return { return {
type: RowType.Header, type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader', getHeaderText: i18n => i18n('findByPhoneNumberHeader'),
}; };
} }
if (virtualRowIndex === 1) { if (virtualRowIndex === 1) {
@ -363,7 +363,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
if (virtualRowIndex === 0) { if (virtualRowIndex === 0) {
return { return {
type: RowType.Header, type: RowType.Header,
i18nKey: 'findByUsernameHeader', getHeaderText: i18n => i18n('findByUsernameHeader'),
}; };
} }
if (virtualRowIndex === 1) { if (virtualRowIndex === 1) {

View file

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

View file

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

View file

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

View file

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

View file

@ -511,7 +511,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
key: string, key: string,
_i18n: unknown, _i18n: unknown,
components: ReplacementValuesType<string> | undefined 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(' ') }; return { text: changes.map(({ text }) => text).join(' ') };

View file

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

View file

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

View file

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

View file

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

View file

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