Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Fix instances of double translation and guard translation calls using typescript #11443

Merged
merged 12 commits into from
Aug 22, 2023
16 changes: 10 additions & 6 deletions src/@types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor

// Utility type for string dot notation for accessing nested object properties
// Based on https://stackoverflow.com/a/58436959
type Join<K, P> = K extends string | number
type Join<K, P, S extends string = "."> = K extends string | number
? P extends string | number
? `${K}${"" extends P ? "" : "."}${P}`
? `${K}${"" extends P ? "" : S}${P}`
: never
: never;

type Prev = [never, 0, 1, 2, 3, ...0[]];

export type Leaves<T, D extends number = 3> = [D] extends [never]
export type Leaves<Target, Separator extends string = ".", LeafType = string, MaxDepth extends number = 3> = [
MaxDepth,
] extends [never]
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
? never
: T extends object
? { [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T]
: "";
: Target extends LeafType
? ""
: {
[K in keyof Target]-?: Join<K, Leaves<Target[K], Separator, LeafType, Prev[MaxDepth]>, Separator>;
}[keyof Target];

export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
Expand Down
4 changes: 2 additions & 2 deletions src/accessibility/KeyboardShortcutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

import { KeyCombo } from "../KeyBindingsManager";
import { IS_MAC, Key } from "../Keyboard";
import { _t, _td } from "../languageHandler";
import { _t, _td, TranslationKey } from "../languageHandler";
import PlatformPeg from "../PlatformPeg";
import SettingsStore from "../settings/SettingsStore";
import {
Expand Down Expand Up @@ -130,5 +130,5 @@ export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | und

export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string);
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as TranslationKey);
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
};
6 changes: 3 additions & 3 deletions src/accessibility/KeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { _td } from "../languageHandler";
import { _td, TranslationKey } from "../languageHandler";
import { IS_MAC, Key } from "../Keyboard";
import { IBaseSetting } from "../settings/Settings";
import { KeyCombo } from "../KeyBindingsManager";
Expand Down Expand Up @@ -160,7 +160,7 @@ type KeyboardShortcutSetting = Omit<IBaseSetting<KeyCombo>, "supportedLevels">;
export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;

export interface ICategory {
categoryLabel?: string;
categoryLabel?: TranslationKey;
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
settingNames: KeyBindingAction[];
}
Expand All @@ -179,7 +179,7 @@ export enum CategoryName {
// Meta-key representing the digits [0-9] often found at the top of standard keyboard layouts
export const DIGITS = "digits";

export const ALTERNATE_KEY_NAME: Record<string, string> = {
export const ALTERNATE_KEY_NAME: Record<string, TranslationKey> = {
[Key.PAGE_UP]: _td("Page Up"),
[Key.PAGE_DOWN]: _td("Page Down"),
[Key.ESCAPE]: _td("Esc"),
Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/EmbeddedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import sanitizeHtml from "sanitize-html";
import classnames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";

import { _t } from "../../languageHandler";
import { _t, TranslationKey } from "../../languageHandler";
import dis from "../../dispatcher/dispatcher";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import MatrixClientContext from "../../contexts/MatrixClientContext";
Expand Down Expand Up @@ -56,7 +56,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
};
}

private translate(s: string): string {
private translate(s: TranslationKey): string {
return sanitizeHtml(_t(s));
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/TabbedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as React from "react";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";

import { _t } from "../../languageHandler";
import { _t, TranslationKey } from "../../languageHandler";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers";
import { NonEmptyArray } from "../../@types/common";
Expand All @@ -40,7 +40,7 @@ export class Tab<T extends string> {
*/
public constructor(
public readonly id: T,
public readonly label: string,
public readonly label: TranslationKey,
public readonly icon: string | null,
public readonly body: React.ReactNode,
public readonly screenName?: ScreenName,
Expand Down
14 changes: 7 additions & 7 deletions src/components/views/auth/EmailField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
import React, { PureComponent, RefCallback, RefObject } from "react";

import Field, { IInputProps } from "../elements/Field";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import * as Email from "../../../email";

Expand All @@ -27,9 +27,9 @@ interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
value: string;
autoFocus?: boolean;

label?: string;
labelRequired?: string;
labelInvalid?: string;
label: TranslationKey;
labelRequired: TranslationKey;
labelInvalid: TranslationKey;

// When present, completely overrides the default validation rules.
validationRules?: (fieldState: IFieldState) => Promise<IValidationResult>;
Expand All @@ -50,12 +50,12 @@ class EmailField extends PureComponent<IProps> {
{
key: "required",
test: ({ value, allowEmpty }) => allowEmpty || !!value,
invalid: () => _t(this.props.labelRequired!),
invalid: () => _t(this.props.labelRequired),
},
{
key: "email",
test: ({ value }) => !value || Email.looksValid(value),
invalid: () => _t(this.props.labelInvalid!),
invalid: () => _t(this.props.labelInvalid),
},
],
});
Expand All @@ -80,7 +80,7 @@ class EmailField extends PureComponent<IProps> {
id={this.props.id}
ref={this.props.fieldRef}
type="text"
label={_t(this.props.label!)}
label={_t(this.props.label)}
value={this.props.value}
autoFocus={this.props.autoFocus}
onChange={this.props.onChange}
Expand Down
8 changes: 4 additions & 4 deletions src/components/views/auth/PassphraseConfirmField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React, { PureComponent, RefCallback, RefObject } from "react";

import Field, { IInputProps } from "../elements/Field";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";

interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
id?: string;
Expand All @@ -27,9 +27,9 @@ interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
value: string;
password: string; // The password we're confirming

label: string;
labelRequired: string;
labelInvalid: string;
label: TranslationKey;
labelRequired: TranslationKey;
labelInvalid: TranslationKey;

onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;
Expand Down
10 changes: 5 additions & 5 deletions src/components/views/auth/PassphraseField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import zxcvbn from "zxcvbn";

import SdkConfig from "../../../SdkConfig";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import Field, { IInputProps } from "../elements/Field";
import { MatrixClientPeg } from "../../../MatrixClientPeg";

Expand All @@ -34,10 +34,10 @@ interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
// Additional strings such as a username used to catch bad passwords
userInputs?: string[];

label: string;
labelEnterPassword: string;
labelStrongPassword: string;
labelAllowedButUnsafe: string;
label: TranslationKey;
labelEnterPassword: TranslationKey;
labelStrongPassword: TranslationKey;
labelAllowedButUnsafe: TranslationKey;

onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/context_menus/DeviceContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import React, { useEffect, useState } from "react";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
import IconizedContextMenu, { IconizedContextMenuOptionList, IconizedContextMenuRadio } from "./IconizedContextMenu";
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";

const SECTION_NAMES: Record<MediaDeviceKindEnum, string> = {
const SECTION_NAMES: Record<MediaDeviceKindEnum, TranslationKey> = {
[MediaDeviceKindEnum.AudioInput]: _td("Input devices"),
[MediaDeviceKindEnum.AudioOutput]: _td("Output devices"),
[MediaDeviceKindEnum.VideoInput]: _td("Cameras"),
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/dialogs/AddExistingToSpaceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Room, EventType } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";

import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import Dropdown from "../elements/Dropdown";
import SearchBox from "../../structures/SearchBox";
Expand Down Expand Up @@ -357,7 +357,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
};

const defaultRendererFactory =
(title: string): Renderer =>
(title: TranslationKey): Renderer =>
(rooms, selectedToAdd, { scrollTop, height }, onChange) =>
(
<div className="mx_AddExistingToSpace_section">
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/dialogs/DevtoolsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.

import React, { useState } from "react";

import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import BaseDialog from "./BaseDialog";
import { TimelineEventEditor } from "./devtools/Event";
Expand All @@ -40,13 +40,13 @@ enum Category {
Other,
}

const categoryLabels: Record<Category, string> = {
const categoryLabels: Record<Category, TranslationKey> = {
[Category.Room]: _td("Room"),
[Category.Other]: _td("Other"),
};

export type Tool = React.FC<IDevtoolsProps> | ((props: IDevtoolsProps) => JSX.Element);
const Tools: Record<Category, [label: string, tool: Tool][]> = {
const Tools: Record<Category, [label: TranslationKey, tool: Tool][]> = {
[Category.Room]: [
[_td("Send custom timeline event"), TimelineEventEditor],
[_td("Explore room state"), RoomStateExplorer],
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/dialogs/TextInputDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
import React, { ChangeEvent, createRef } from "react";

import Field from "../elements/Field";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import { IFieldState, IValidationResult } from "../elements/Validation";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
Expand All @@ -28,7 +28,7 @@ interface IProps {
value: string;
placeholder?: string;
button?: string;
busyMessage: string; // pass _td string
busyMessage: TranslationKey;
focus: boolean;
hasCancel: boolean;
validator?: (fieldState: IFieldState) => Promise<IValidationResult>; // result of withValidation
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/dialogs/devtools/Event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ limitations under the License.
import React, { ChangeEvent, ReactNode, useContext, useMemo, useRef, useState } from "react";
import { IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";

import { _t, _td } from "../../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../../languageHandler";
import Field from "../../elements/Field";
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
Expand All @@ -37,7 +37,7 @@ interface IEventEditorProps extends Pick<IDevtoolsProps, "onBack"> {

interface IFieldDef {
id: string;
label: string; // _td
label: TranslationKey;
default?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import { VerificationPhase as Phase, VerificationRequestEvent } from "matrix-js-
import { CryptoEvent } from "matrix-js-sdk/src/crypto";

import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../../hooks/useEventEmitter";
import { _t, _td } from "../../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../../languageHandler";
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
import { Tool } from "../DevtoolsDialog";

const PHASE_MAP: Record<Phase, string> = {
const PHASE_MAP: Record<Phase, TranslationKey> = {
[Phase.Unsent]: _td("Unsent"),
[Phase.Requested]: _td("Requested"),
[Phase.Ready]: _td("Ready"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.
import React from "react";
import classNames from "classnames";

import { _t } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import BaseDialog from "..//dialogs/BaseDialog";
import DialogButtons from "./DialogButtons";
import AccessibleButton from "./AccessibleButton";
Expand Down Expand Up @@ -135,7 +135,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
this.props.onFinished();
};

private getTab(type: TabId, label: string): Tab<TabId> {
private getTab(type: TabId, label: TranslationKey): Tab<TabId> {
const sources = this.state.sources
.filter((source) => source.id.startsWith(type))
.map((source) => {
Expand All @@ -154,8 +154,8 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI

public render(): React.ReactNode {
const tabs: NonEmptyArray<Tab<TabId>> = [
this.getTab("screen", _t("Share entire screen")),
this.getTab("window", _t("Application window")),
this.getTab("screen", _td("Share entire screen")),
this.getTab("window", _td("Application window")),
Comment on lines +157 to +158
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a bug fix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this fixes the string being passed through the translation function twice (once here and once by TabbedView)

];

return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/messages/DownloadActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Icon as DownloadIcon } from "../../../../res/img/download.svg";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
import Spinner from "../elements/Spinner";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import { FileDownloader } from "../../../utils/FileDownloader";

interface IProps {
Expand All @@ -37,7 +37,7 @@ interface IProps {
interface IState {
loading: boolean;
blob?: Blob;
tooltip: string;
tooltip: TranslationKey;
}

export default class DownloadActionButton extends React.PureComponent<IProps, IState> {
Expand Down
8 changes: 4 additions & 4 deletions src/components/views/rooms/E2EIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ limitations under the License.
import React, { CSSProperties, useState } from "react";
import classNames from "classnames";

import { _t, _td } from "../../../languageHandler";
import { _t, _td, TranslationKey } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import Tooltip, { Alignment } from "../elements/Tooltip";
import { E2EStatus } from "../../../utils/ShieldUtils";
Expand All @@ -32,12 +32,12 @@ export enum E2EState {
Unauthenticated = "unauthenticated",
}

const crossSigningUserTitles: { [key in E2EState]?: string } = {
const crossSigningUserTitles: { [key in E2EState]?: TranslationKey } = {
[E2EState.Warning]: _td("This user has not verified all of their sessions."),
[E2EState.Normal]: _td("You have not verified this user."),
[E2EState.Verified]: _td("You have verified this user. This user has verified all of their sessions."),
};
const crossSigningRoomTitles: { [key in E2EState]?: string } = {
const crossSigningRoomTitles: { [key in E2EState]?: TranslationKey } = {
[E2EState.Warning]: _td("Someone is using an unknown session"),
[E2EState.Normal]: _td("This room is end-to-end encrypted"),
[E2EState.Verified]: _td("Everyone in this room is verified"),
Expand Down Expand Up @@ -85,7 +85,7 @@ const E2EIcon: React.FC<XOR<UserProps, RoomProps>> = ({
className,
);

let e2eTitle: string | undefined;
let e2eTitle: TranslationKey | undefined;
if (isUser) {
e2eTitle = crossSigningUserTitles[status];
} else {
Expand Down
Loading