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
43 changes: 31 additions & 12 deletions src/@types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,41 @@ export type Writeable<T> = { -readonly [P in keyof T]: T[P] };

export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;

// 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
/**
* Utility type for string dot notation for accessing nested object properties.
* Based on https://stackoverflow.com/a/58436959
* @example
* {
* "a": {
* "b": {
* "c": "value"
* },
* "d": "foobar"
* }
* }
* will yield a type of `"a.b.c" | "a.d"` with Separator="."
* @typeParam Target the target type to generate leaf keys for
* @typeParam Separator the separator to use between key segments when accessing nested objects
* @typeParam LeafType the type which leaves of this object extend, used to determine when to stop recursion
* @typeParam MaxDepth the maximum depth to recurse to
* @returns a union type representing all dot (Separator) string notation keys which can access a Leaf (of LeafType)
*/
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
: Target extends LeafType
? ""
: {
[K in keyof Target]-?: Join<K, Leaves<Target[K], Separator, LeafType, Prev[MaxDepth]>, Separator>;
}[keyof Target];
type Prev = [never, 0, 1, 2, 3, ...0[]];
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]
? never
: T extends object
? { [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T]
: "";

export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? RecursivePartial<U>[]
Expand Down
4 changes: 1 addition & 3 deletions src/IdentityAuthClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,7 @@ export default class IdentityAuthClient {
<div>
<p>
{_t(
"This action requires accessing the default identity server " +
"<server /> to validate an email address or phone number, " +
"but the server does not have any terms of service.",
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.",
{},
{
server: () => <b>{abbreviateUrl(identityServerUrl)}</b>,
Expand Down
12 changes: 3 additions & 9 deletions src/LegacyCallHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -823,19 +823,14 @@ export default class LegacyCallHandler extends EventEmitter {
<div>
<p>
{_t(
"Please ask the administrator of your homeserver " +
"(<code>%(homeserverDomain)s</code>) to configure a TURN server in " +
"order for calls to work reliably.",
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.",
{ homeserverDomain: cli.getDomain() },
{ code: (sub: string) => <code>{sub}</code> },
)}
</p>
<p>
{_t(
"Alternatively, you can try to use the public server at " +
"<server/>, but this will not be as reliable, and " +
"it will share your IP address with that server. You can also manage " +
"this in Settings.",
"Alternatively, you can try to use the public server at <server/>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.",
undefined,
{ server: () => <code>{new URL(FALLBACK_ICE_SERVER).pathname}</code> },
)}
Expand Down Expand Up @@ -865,8 +860,7 @@ export default class LegacyCallHandler extends EventEmitter {
description = (
<div>
{_t(
"Call failed because microphone could not be accessed. " +
"Check that a microphone is plugged in and set up correctly.",
"Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.",
)}
</div>
);
Expand Down
3 changes: 1 addition & 2 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,7 @@ export function attemptTokenLogin(
logger.warn("Cannot log in with token: can't determine HS URL to use");
onFailedDelegatedAuthLogin(
_t(
"We asked the browser to remember which homeserver you use to let you sign in, " +
"but unfortunately your browser has forgotten it. Go to the sign in page and try again.",
"We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.",
),
);
return Promise.resolve(false);
Expand Down
3 changes: 1 addition & 2 deletions src/Notifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,7 @@ class NotifierClass {
const description =
result === "denied"
? _t(
"%(brand)s does not have permission to send you notifications - " +
"please check your browser settings",
"%(brand)s does not have permission to send you notifications - please check your browser settings",
{ brand },
)
: _t("%(brand)s was not given permission to send notifications - please try again", {
Expand Down
14 changes: 4 additions & 10 deletions src/SlashCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ export const Commands = [
const unixTimestamp = Date.parse(args);
if (!unixTimestamp) {
throw new UserFriendlyError(
"We were unable to understand the given date (%(inputDate)s). " +
"Try using the format YYYY-MM-DD.",
"We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.",
{ inputDate: args, cause: undefined },
);
}
Expand Down Expand Up @@ -401,9 +400,7 @@ export const Commands = [
description: (
<p>
{_t(
"Use an identity server to invite by email. " +
"Click continue to use the default identity server " +
"(%(defaultIdentityServerName)s) or manage in Settings.",
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.",
{
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
},
Expand Down Expand Up @@ -717,9 +714,7 @@ export const Commands = [
if (device.getFingerprint() !== fingerprint) {
const fprint = device.getFingerprint();
throw new UserFriendlyError(
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session" +
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
'"%(fingerprint)s". This could mean your communications are being intercepted!',
'WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is "%(fprint)s" which does not match the provided key "%(fingerprint)s". This could mean your communications are being intercepted!',
{
fprint,
userId,
Expand All @@ -739,8 +734,7 @@ export const Commands = [
<div>
<p>
{_t(
"The signing key you provided matches the signing key you received " +
"from %(userId)s's session %(deviceId)s. Session marked as verified.",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.",
{ userId, deviceId },
)}
</p>
Expand Down
31 changes: 13 additions & 18 deletions src/TextForEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,17 +482,14 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): (() => string) | nul
case HistoryVisibility.Invited:
return () =>
_t(
"%(senderName)s made future room history visible to all room members, " +
"from the point they are invited.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.",
{ senderName },
);
case HistoryVisibility.Joined:
return () =>
_t(
"%(senderName)s made future room history visible to all room members, " +
"from the point they joined.",
{ senderName },
);
_t("%(senderName)s made future room history visible to all room members, from the point they joined.", {
senderName,
});
case HistoryVisibility.Shared:
return () => _t("%(senderName)s made future room history visible to all room members.", { senderName });
case HistoryVisibility.WorldReadable:
Expand Down Expand Up @@ -810,33 +807,31 @@ function textForMjolnirEvent(event: MatrixEvent): (() => string) | null {
if (USER_RULE_TYPES.includes(event.getType())) {
return () =>
_t(
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
{ senderName, oldGlob: prevEntity, newGlob: entity, reason },
);
} else if (ROOM_RULE_TYPES.includes(event.getType())) {
return () =>
_t(
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
{ senderName, oldGlob: prevEntity, newGlob: entity, reason },
);
} else if (SERVER_RULE_TYPES.includes(event.getType())) {
return () =>
_t(
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s",
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
{ senderName, oldGlob: prevEntity, newGlob: entity, reason },
);
}

// Unknown type. We'll say something but we shouldn't end up here.
return () =>
_t(
"%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s " +
"for %(reason)s",
{ senderName, oldGlob: prevEntity, newGlob: entity, reason },
);
_t("%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", {
senderName,
oldGlob: prevEntity,
newGlob: entity,
reason,
});
}

export function textForLocationEvent(event: MatrixEvent): () => string {
Expand Down
6 changes: 3 additions & 3 deletions src/accessibility/KeyboardShortcutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {
IKeyboardShortcuts,
KeyBindingAction,
KEYBOARD_SHORTCUTS,
KeyboardShortcutSetting,
MAC_ONLY_SHORTCUTS,
} from "./KeyboardShortcuts";
import { IBaseSetting } from "../settings/Settings";

/**
* This function gets the keyboard shortcuts that should be presented in the UI
Expand Down Expand Up @@ -115,7 +115,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [
KeyBindingAction,
IBaseSetting<KeyCombo>,
KeyboardShortcutSetting,
][];

return entries.reduce((acc, [key, value]) => {
Expand All @@ -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);
};
10 changes: 6 additions & 4 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 @@ -154,13 +154,15 @@ export enum KeyBindingAction {
ToggleHiddenEventVisibility = "KeyBinding.toggleHiddenEventVisibility",
}

type KeyboardShortcutSetting = Omit<IBaseSetting<KeyCombo>, "supportedLevels">;
export type KeyboardShortcutSetting = Omit<IBaseSetting<KeyCombo>, "supportedLevels" | "displayName"> & {
displayName?: TranslationKey;
};

// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
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 +181,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
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
<form onSubmit={this.onChooseKeyPassphraseFormSubmit}>
<p className="mx_CreateSecretStorageDialog_centeredBody">
{_t(
"Safeguard against losing access to encrypted messages & data by " +
"backing up encryption keys on your server.",
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
)}
</p>
<div className="mx_CreateSecretStorageDialog_primaryContainer" role="radiogroup">
Expand Down Expand Up @@ -611,9 +610,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
<form onSubmit={this.onMigrateFormSubmit}>
<p>
{_t(
"Upgrade this session to allow it to verify other sessions, " +
"granting them access to encrypted messages and marking them " +
"as trusted for other users.",
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
)}
</p>
<div>{authPrompt}</div>
Expand All @@ -636,8 +633,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
<form onSubmit={this.onPassPhraseNextClick}>
<p>
{_t(
"Enter a Security Phrase only you know, as it's used to safeguard your data. " +
"To be secure, you shouldn't re-use your account password.",
"Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.",
)}
</p>

Expand Down Expand Up @@ -752,8 +748,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
<div>
<p>
{_t(
"Store your Security Key somewhere safe, like a password manager or a safe, " +
"as it's used to safeguard your encrypted data.",
"Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
)}
</p>
<div className="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer">
Expand Down
Loading
Loading