Make slash command errors translatable but also work in rageshakes (#7377)

See https://github.com/matrix-org/matrix-react-sdk/pull/7372#discussion_r769556546

We want the error to be translated for the user but not in our rageshake logs.

Also updates some error messages to give more info.
pull/21833/head
Eric Eastwood 2022-01-11 12:25:28 -06:00 committed by GitHub
parent 53a72dafa9
commit 038a6bc204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 40 deletions

View File

@ -29,6 +29,9 @@
"matrix_src_main": "./src/index.ts", "matrix_src_main": "./src/index.ts",
"matrix_lib_main": "./lib/index.ts", "matrix_lib_main": "./lib/index.ts",
"matrix_lib_typings": "./lib/index.d.ts", "matrix_lib_typings": "./lib/index.d.ts",
"matrix_i18n_extra_translation_funcs": [
"newTranslatableError"
],
"scripts": { "scripts": {
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
"i18n": "matrix-gen-i18n", "i18n": "matrix-gen-i18n",

View File

@ -27,7 +27,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg'; import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import { _t, _td } from './languageHandler'; import { _t, _td, newTranslatableError } from './languageHandler';
import Modal from './Modal'; import Modal from './Modal';
import MultiInviter from './utils/MultiInviter'; import MultiInviter from './utils/MultiInviter';
import { linkifyAndSanitizeHtml } from './HtmlUtils'; import { linkifyAndSanitizeHtml } from './HtmlUtils';
@ -141,13 +141,26 @@ export class Command {
run(roomId: string, threadId: string, args: string) { run(roomId: string, threadId: string, args: string) {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) return reject(_t("Command error")); if (!this.runFn) {
reject(
newTranslatableError(
"Command error: Unable to handle slash command.",
),
);
return;
}
const renderingType = threadId const renderingType = threadId
? TimelineRenderingType.Thread ? TimelineRenderingType.Thread
: TimelineRenderingType.Room; : TimelineRenderingType.Room;
if (this.renderingTypes && !this.renderingTypes?.includes(renderingType)) { if (this.renderingTypes && !this.renderingTypes?.includes(renderingType)) {
return reject(_t("Command error")); return reject(
newTranslatableError(
"Command error: Unable to find rendering type (%(renderingType)s)",
{ renderingType },
),
);
} }
return this.runFn.bind(this)(roomId, args); return this.runFn.bind(this)(roomId, args);
@ -270,7 +283,9 @@ export const Commands = [
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) { if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject(_t("You do not have the required permissions to use this command.")); return reject(
newTranslatableError("You do not have the required permissions to use this command."),
);
} }
const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation', const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
@ -297,15 +312,10 @@ export const Commands = [
return success((async () => { return success((async () => {
const unixTimestamp = Date.parse(args); const unixTimestamp = Date.parse(args);
if (!unixTimestamp) { if (!unixTimestamp) {
throw new Error( throw newTranslatableError(
// FIXME: Use newTranslatableError here instead 'We were unable to understand the given date (%(inputDate)s). ' +
// otherwise the rageshake error messages will be 'Try using the format YYYY-MM-DD.',
// translated too
_t(
// eslint-disable-next-line max-len
'We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.',
{ inputDate: args }, { inputDate: args },
),
); );
} }
@ -437,7 +447,11 @@ export const Commands = [
return success(cli.setRoomTopic(roomId, args)); return success(cli.setRoomTopic(roomId, args));
} }
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) return reject(_t("Failed to set topic")); if (!room) {
return reject(
newTranslatableError("Failed to get room topic: Unable to find room (%(roomId)s", { roomId }),
);
}
const topicEvents = room.currentState.getStateEvents('m.room.topic', ''); const topicEvents = room.currentState.getStateEvents('m.room.topic', '');
const topic = topicEvents && topicEvents.getContent().topic; const topic = topicEvents && topicEvents.getContent().topic;
@ -509,10 +523,16 @@ export const Commands = [
useDefaultIdentityServer(); useDefaultIdentityServer();
return; return;
} }
throw new Error(_t("Use an identity server to invite by email. Manage in Settings.")); throw newTranslatableError(
"Use an identity server to invite by email. Manage in Settings.",
);
}); });
} else { } else {
return reject(_t("Use an identity server to invite by email. Manage in Settings.")); return reject(
newTranslatableError(
"Use an identity server to invite by email. Manage in Settings.",
),
);
} }
} }
const inviter = new MultiInviter(roomId); const inviter = new MultiInviter(roomId);
@ -680,7 +700,14 @@ export const Commands = [
} }
if (targetRoomId) break; if (targetRoomId) break;
} }
if (!targetRoomId) return reject(_t('Unrecognised room address:') + ' ' + roomAlias); if (!targetRoomId) {
return reject(
newTranslatableError(
'Unrecognised room address: %(roomAlias)s',
{ roomAlias },
),
);
}
} }
} }
@ -819,10 +846,14 @@ export const Commands = [
if (!isNaN(powerLevel)) { if (!isNaN(powerLevel)) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) return reject(_t("Command failed")); if (!room) {
return reject(
newTranslatableError("Command failed: Unable to find room (%(roomId)s", { roomId }),
);
}
const member = room.getMember(userId); const member = room.getMember(userId);
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) { if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
return reject(_t("Could not find user in room")); return reject(newTranslatableError("Could not find user in room"));
} }
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent)); return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
@ -849,10 +880,16 @@ export const Commands = [
if (matches) { if (matches) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) return reject(_t("Command failed")); if (!room) {
return reject(
newTranslatableError("Command failed: Unable to find room (%(roomId)s", { roomId }),
);
}
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room")); if (!powerLevelEvent.getContent().users[args]) {
return reject(newTranslatableError("Could not find user in room"));
}
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
} }
} }
@ -877,7 +914,7 @@ export const Commands = [
isEnabled: () => SettingsStore.getValue(UIFeature.Widgets), isEnabled: () => SettingsStore.getValue(UIFeature.Widgets),
runFn: function(roomId, widgetUrl) { runFn: function(roomId, widgetUrl) {
if (!widgetUrl) { if (!widgetUrl) {
return reject(_t("Please supply a widget URL or embed code")); return reject(newTranslatableError("Please supply a widget URL or embed code"));
} }
// Try and parse out a widget URL from iframes // Try and parse out a widget URL from iframes
@ -896,7 +933,7 @@ export const Commands = [
} }
if (!widgetUrl.startsWith("https://") && !widgetUrl.startsWith("http://")) { if (!widgetUrl.startsWith("https://") && !widgetUrl.startsWith("http://")) {
return reject(_t("Please supply a https:// or http:// widget URL")); return reject(newTranslatableError("Please supply a https:// or http:// widget URL"));
} }
if (WidgetUtils.canUserModifyWidgets(roomId)) { if (WidgetUtils.canUserModifyWidgets(roomId)) {
const userId = MatrixClientPeg.get().getUserId(); const userId = MatrixClientPeg.get().getUserId();
@ -918,7 +955,7 @@ export const Commands = [
return success(WidgetUtils.setRoomWidget(roomId, widgetId, type, widgetUrl, name, data)); return success(WidgetUtils.setRoomWidget(roomId, widgetId, type, widgetUrl, name, data));
} else { } else {
return reject(_t("You cannot modify widgets in this room.")); return reject(newTranslatableError("You cannot modify widgets in this room."));
} }
}, },
category: CommandCategories.admin, category: CommandCategories.admin,
@ -941,22 +978,25 @@ export const Commands = [
return success((async () => { return success((async () => {
const device = cli.getStoredDevice(userId, deviceId); const device = cli.getStoredDevice(userId, deviceId);
if (!device) { if (!device) {
throw new Error(_t('Unknown (user, session) pair:') + ` (${userId}, ${deviceId})`); throw newTranslatableError(
'Unknown (user, session) pair: (%(userId)s, %(deviceId)s)',
{ userId, deviceId },
);
} }
const deviceTrust = await cli.checkDeviceTrust(userId, deviceId); const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
if (deviceTrust.isVerified()) { if (deviceTrust.isVerified()) {
if (device.getFingerprint() === fingerprint) { if (device.getFingerprint() === fingerprint) {
throw new Error(_t('Session already verified!')); throw newTranslatableError('Session already verified!');
} else { } else {
throw new Error(_t('WARNING: Session already verified, but keys do NOT MATCH!')); throw newTranslatableError('WARNING: Session already verified, but keys do NOT MATCH!');
} }
} }
if (device.getFingerprint() !== fingerprint) { if (device.getFingerprint() !== fingerprint) {
const fprint = device.getFingerprint(); const fprint = device.getFingerprint();
throw new Error( throw newTranslatableError(
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' + 'WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' +
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' + ' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
'"%(fingerprint)s". This could mean your communications are being intercepted!', '"%(fingerprint)s". This could mean your communications are being intercepted!',
{ {
@ -964,7 +1004,8 @@ export const Commands = [
userId, userId,
deviceId, deviceId,
fingerprint, fingerprint,
})); },
);
} }
await cli.setDeviceVerified(userId, deviceId, true); await cli.setDeviceVerified(userId, deviceId, true);
@ -1083,7 +1124,7 @@ export const Commands = [
if (isPhoneNumber) { if (isPhoneNumber) {
const results = await CallHandler.instance.pstnLookup(this.state.value); const results = await CallHandler.instance.pstnLookup(this.state.value);
if (!results || results.length === 0 || !results[0].userid) { if (!results || results.length === 0 || !results[0].userid) {
throw new Error("Unable to find Matrix ID for phone number"); throw newTranslatableError("Unable to find Matrix ID for phone number");
} }
userId = results[0].userid; userId = results[0].userid;
} }
@ -1135,7 +1176,7 @@ export const Commands = [
runFn: function(roomId, args) { runFn: function(roomId, args) {
const call = CallHandler.instance.getCallForRoom(roomId); const call = CallHandler.instance.getCallForRoom(roomId);
if (!call) { if (!call) {
return reject("No active call in this room"); return reject(newTranslatableError("No active call in this room"));
} }
call.setRemoteOnHold(true); call.setRemoteOnHold(true);
return success(); return success();
@ -1149,7 +1190,7 @@ export const Commands = [
runFn: function(roomId, args) { runFn: function(roomId, args) {
const call = CallHandler.instance.getCallForRoom(roomId); const call = CallHandler.instance.getCallForRoom(roomId);
if (!call) { if (!call) {
return reject("No active call in this room"); return reject(newTranslatableError("No active call in this room"));
} }
call.setRemoteOnHold(false); call.setRemoteOnHold(false);
return success(); return success();

View File

@ -378,6 +378,9 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
let errText; let errText;
if (typeof error === 'string') { if (typeof error === 'string') {
errText = error; errText = error;
} else if (error.translatedMessage) {
// Check for translatable errors (newTranslatableError)
errText = error.translatedMessage;
} else if (error.message) { } else if (error.message) {
errText = error.message; errText = error.message;
} else { } else {

View File

@ -424,7 +424,8 @@
"Advanced": "Advanced", "Advanced": "Advanced",
"Effects": "Effects", "Effects": "Effects",
"Other": "Other", "Other": "Other",
"Command error": "Command error", "Command error: Unable to handle slash command.": "Command error: Unable to handle slash command.",
"Command error: Unable to find rendering type (%(renderingType)s)": "Command error: Unable to find rendering type (%(renderingType)s)",
"Usage": "Usage", "Usage": "Usage",
"Sends the given message as a spoiler": "Sends the given message as a spoiler", "Sends the given message as a spoiler": "Sends the given message as a spoiler",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
@ -443,7 +444,7 @@
"Changes your avatar in this current room only": "Changes your avatar in this current room only", "Changes your avatar in this current room only": "Changes your avatar in this current room only",
"Changes your avatar in all rooms": "Changes your avatar in all rooms", "Changes your avatar in all rooms": "Changes your avatar in all rooms",
"Gets or sets the room topic": "Gets or sets the room topic", "Gets or sets the room topic": "Gets or sets the room topic",
"Failed to set topic": "Failed to set topic", "Failed to get room topic: Unable to find room (%(roomId)s": "Failed to get room topic: Unable to find room (%(roomId)s",
"This room has no topic.": "This room has no topic.", "This room has no topic.": "This room has no topic.",
"Sets the room name": "Sets the room name", "Sets the room name": "Sets the room name",
"Invites user with given id to current room": "Invites user with given id to current room", "Invites user with given id to current room": "Invites user with given id to current room",
@ -452,7 +453,7 @@
"Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.", "Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.",
"Joins room with given address": "Joins room with given address", "Joins room with given address": "Joins room with given address",
"Leave room": "Leave room", "Leave room": "Leave room",
"Unrecognised room address:": "Unrecognised room address:", "Unrecognised room address: %(roomAlias)s": "Unrecognised room address: %(roomAlias)s",
"Kicks user with given id": "Kicks user with given id", "Kicks user with given id": "Kicks user with given id",
"Bans user with given id": "Bans user with given id", "Bans user with given id": "Bans user with given id",
"Unbans user with given ID": "Unbans user with given ID", "Unbans user with given ID": "Unbans user with given ID",
@ -463,7 +464,7 @@
"Unignored user": "Unignored user", "Unignored user": "Unignored user",
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s", "You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
"Define the power level of a user": "Define the power level of a user", "Define the power level of a user": "Define the power level of a user",
"Command failed": "Command failed", "Command failed: Unable to find room (%(roomId)s": "Command failed: Unable to find room (%(roomId)s",
"Could not find user in room": "Could not find user in room", "Could not find user in room": "Could not find user in room",
"Deops user with given id": "Deops user with given id", "Deops user with given id": "Deops user with given id",
"Opens the Developer Tools dialog": "Opens the Developer Tools dialog", "Opens the Developer Tools dialog": "Opens the Developer Tools dialog",
@ -472,7 +473,7 @@
"Please supply a https:// or http:// widget URL": "Please supply a https:// or http:// widget URL", "Please supply a https:// or http:// widget URL": "Please supply a https:// or http:// widget URL",
"You cannot modify widgets in this room.": "You cannot modify widgets in this room.", "You cannot modify widgets in this room.": "You cannot modify widgets in this room.",
"Verifies a user, session, and pubkey tuple": "Verifies a user, session, and pubkey tuple", "Verifies a user, session, and pubkey tuple": "Verifies a user, session, and pubkey tuple",
"Unknown (user, session) pair:": "Unknown (user, session) pair:", "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)": "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)",
"Session already verified!": "Session already verified!", "Session already verified!": "Session already verified!",
"WARNING: Session already verified, but keys do NOT MATCH!": "WARNING: Session already verified, but keys do NOT MATCH!", "WARNING: Session already verified, but keys do NOT MATCH!": "WARNING: Session already verified, but keys do NOT MATCH!",
"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!", "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!",
@ -485,8 +486,10 @@
"Displays information about a user": "Displays information about a user", "Displays information about a user": "Displays information about a user",
"Send a bug report with logs": "Send a bug report with logs", "Send a bug report with logs": "Send a bug report with logs",
"Opens chat with the given user": "Opens chat with the given user", "Opens chat with the given user": "Opens chat with the given user",
"Unable to find Matrix ID for phone number": "Unable to find Matrix ID for phone number",
"Sends a message to the given user": "Sends a message to the given user", "Sends a message to the given user": "Sends a message to the given user",
"Places the call in the current room on hold": "Places the call in the current room on hold", "Places the call in the current room on hold": "Places the call in the current room on hold",
"No active call in this room": "No active call in this room",
"Takes the call in the current room off hold": "Takes the call in the current room off hold", "Takes the call in the current room off hold": "Takes the call in the current room off hold",
"Converts the room to a DM": "Converts the room to a DM", "Converts the room to a DM": "Converts the room to a DM",
"Converts the DM to a room": "Converts the DM to a room", "Converts the DM to a room": "Converts the DM to a room",
@ -1630,6 +1633,7 @@
"This room is end-to-end encrypted": "This room is end-to-end encrypted", "This room is end-to-end encrypted": "This room is end-to-end encrypted",
"Everyone in this room is verified": "Everyone in this room is verified", "Everyone in this room is verified": "Everyone in this room is verified",
"Server error": "Server error", "Server error": "Server error",
"Command error": "Command error",
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
"Unknown Command": "Unknown Command", "Unknown Command": "Unknown Command",
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s", "Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",

View File

@ -53,9 +53,9 @@ interface ITranslatableError extends Error {
* @param {string} message Message to translate. * @param {string} message Message to translate.
* @returns {Error} The constructed error. * @returns {Error} The constructed error.
*/ */
export function newTranslatableError(message: string) { export function newTranslatableError(message: string, variables?: IVariables): ITranslatableError {
const error = new Error(message) as ITranslatableError; const error = new Error(message) as ITranslatableError;
error.translatedMessage = _t(message); error.translatedMessage = _t(message, variables);
return error; return error;
} }