Move strings to be translatable and lint

This commit is contained in:
Zack Rauen 2022-10-13 23:42:05 -04:00
parent 7959260f29
commit 90338e9b84
12 changed files with 99 additions and 76 deletions

View File

@ -1,5 +1,6 @@
{
"Panels": {
"updates": "Updates",
"plugins": "Plugins",
"themes": "Themes",
"customcss": "Custom CSS"
@ -237,6 +238,7 @@
"metaNotFound": "META was not found.",
"compileError": "Could not be compiled. See console for details.",
"wasUnloaded": "{{name}} was unloaded.",
"wasLoaded": "{{name}} v{{version}} was loaded.",
"blankSlateHeader": "You don't have any {{type}}s!",
"blankSlateMessage": "Grab some from [this website]({{link}}) and add them to your {{type}} folder."
},
@ -258,7 +260,7 @@
"failureMessage": "BetterDiscord failed to download emotes, please check your internet connection and firewall."
},
"PublicServers": {
"button": "public",
"button": "Public Servers",
"join": "Join",
"joining": "Joining",
"joined": "Joined",
@ -295,6 +297,9 @@
"additionalInfo": "Additional Info",
"restartPrompt": "In order to take effect, Discord needs to be restarted. Do you want to restart now?"
},
"Notices": {
"moreInfo": "More Info"
},
"ReactDevTools": {
"notFound": "Extension Not Found",
"notFoundDetails": "Unable to find the React Developer Tools extension on your PC. Please install the extension on your local Chrome installation."
@ -305,17 +310,21 @@
"ascending": "Ascending",
"descending": "Descending"
},
"Startup": {
"notSupported": "Not Supported",
"incompatibleApp": "BetterDiscord does not work with {{app}}. Please uninstall one of them.",
"updateNow": "Update Now",
"maybeLater": "Maybe Later",
"updateAvailable": "Update Available",
"updateInfo": "There is an update available for BetterDiscord's Injector ({{version}}).\n\nYou can either update and restart now, or later.",
"updateFailed": "Could Not Update",
"manualUpdate": "Unable to update automatically, please download the installer and reinstall normally.\n\n[Download Installer](https://github.com/rauenzi/BetterDiscordApp/releases/latest)",
"jqueryFailed": "jQuery Failed To Load",
"jqueryFailedDetails": "jQuery could not be loaded, and some plugins may not work properly. Proceed at your own risk."
"Updater": {
"updateFailed": "Update Failed!",
"updateFailedMessage": "BetterDiscord failed to update. Please download the latest version of the installer from our website (https://betterdiscord.app/) and reinstall.",
"updateSuccessful": "Update Successful!",
"updateAvailable": "BetterDiscord has a new update (v{{version}})",
"addonUpdatesAvailable": "BetterDiscord has found updates for {{count}} of your {{.type}}s!",
"addonUpdated": "{{name}} has been updated to version {{version}}!",
"checking": "Checking for updates!",
"finishedChecking": "Finished checking for updates!",
"checkForUpdates": "Check For Updates!",
"updateAll": "Update All!",
"noUpdatesAvailable": "No updates available.",
"versionAvailable": "Version {{version}} now available!",
"upToDateBlankslate": "All of your {{type}} seem to be up to date!",
"updateButton": "Update!"
},
"WindowPrefs": {
"enabledInfo": "This option requires a transparent theme in order to work properly. On Windows this may break your aero snapping and maximizing.\n\nIn order to take effect, Discord needs to be restarted. Do you want to restart now?",

View File

@ -35,10 +35,10 @@ const makeRequest = (url, options, callback, setReq) => {
});
});
req.end();
}
};
const request = function (url, options, callback) {
let responseObject = undefined;
let responseObject = null;
let reqObject = null;
let pipe = null;
@ -56,7 +56,8 @@ const request = function (url, options, callback) {
pipe(fsStream) {
if (!responseObject) {
pipe = fsStream;
} else {
}
else {
responseObject.pipe(fsStream);
}
}

View File

@ -1,5 +1,7 @@
import {webFrame} from "electron";
/* global window:false */
export default function () {
const patcher = function () {
const chunkName = "webpackChunkdiscord_app";
@ -7,21 +9,24 @@ export default function () {
const value = target[prop];
Object.defineProperty(target, prop, {
get() {return value;},
set(value) {
set(newValue) {
Object.defineProperty(target, prop, {
value,
value: newValue,
configurable: true,
enumerable: true,
writable: true
});
try {
effect(value);
} catch (error) {
effect(newValue);
}
catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
return value;
// eslint-disable-next-line no-setter-return
return newValue;
},
configurable: true
});
@ -41,7 +46,7 @@ export default function () {
configurable: true
});
}
}
};
}]);
instance.pop();

View File

@ -60,7 +60,7 @@ export default new class PublicServers extends Builtin {
{
id: "public-servers-button",
onClick: () => this.openPublicServers(),
text: "Public Servers",
text: Strings.PublicServers.button,
icon: () => React.createElement(Globe, {color: "currentColor"})
}
)
@ -93,8 +93,10 @@ export default new class PublicServers extends Builtin {
aSlot.dataset.listItemId = "public-servers";
// Remove any badges
const badge = newButton.querySelector(`[class*="premiumTrial"]`);
badge?.remove?.();
const premiumBadge = newButton.querySelector(`[class*="premiumTrial"]`);
premiumBadge?.remove?.();
const numberBadge = newButton.querySelector(`[class*="numberBadge-"]`);
numberBadge?.remove?.();
// Render our icon in the avatar slot
const avatarSlot = newButton.querySelector(`[class*="avatar-"]`);
@ -104,15 +106,18 @@ export default new class PublicServers extends Builtin {
// Replace the existing name
const nameSlot = newButton.querySelector(`[class*="name-"]`);
nameSlot.textContent = "Public Servers";
nameSlot.textContent = Strings.PublicServers.button;
// Insert before the header, end of the list
header.parentNode.insertBefore(newButton, header);
this.button = newButton;
}
disabled() {
this.unpatchAll();
// DOM.query("#bd-pub-li").remove();
this.button?.remove?.();
document.querySelector("#public-servers-button")?.parentElement?.parentElement?.remove?.();
}
async _appendButton() {
@ -130,12 +135,4 @@ export default new class PublicServers extends Builtin {
openPublicServers() {
LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer}));
}
get button() {
const btn = DOMManager.parseHTML(`<div id="bd-pub-li" class="${DiscordModules.GuildClasses.listItem}">`);
const label = DOMManager.parseHTML(`<div id="bd-pub-button" class="${DiscordModules.GuildClasses.wrapper + " " + DiscordModules.GuildClasses.circleIconButton}">${Strings.PublicServers.button}</div>`);
label.addEventListener("click", () => {this.openPublicServers();});
btn.append(label);
return btn;
}
};

View File

@ -220,7 +220,7 @@ export default class AddonManager {
return error;
}
if (shouldToast) Toasts.success(`${addon.name} v${addon.version} was loaded.`);
if (shouldToast) Toasts.success(Strings.Addons.wasUnloaded.format({name: addon.name, version: addon.version}));
this.emit("loaded", addon);
if (!this.state[addon.id]) return this.state[addon.id] = false;
@ -235,7 +235,7 @@ export default class AddonManager {
this.addonList.splice(this.addonList.indexOf(addon), 1);
this.emit("unloaded", addon);
if (shouldToast) Toasts.success(`${addon.name} was unloaded.`);
if (shouldToast) Toasts.success(Strings.Addons.wasUnloaded.format({name: addon.name}));
return true;
}

View File

@ -30,7 +30,7 @@ const MenuComponents = (() => {
const ContextMenuActions = (() => {
const out = {};
const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(m => typeof m === "function" && m.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
for (const key of Object.keys(ActionsModule)) {
if (ActionsModule[key].toString().includes("CONTEXT_MENU_CLOSE")) {
@ -51,10 +51,10 @@ class MenuPatcher {
static initialize() {
const {module, key} = (() => {
const module = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
const key = Object.keys(module).find(key => module[key].length === 3);
const foundModule = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
const foundKey = Object.keys(foundModule).find(k => foundModule[k].length === 3);
return {module, key};
return {module: foundModule, key: foundKey};
})();
Patcher.before("ContextMenuPatcher", module, key, (_, methodArguments) => {

View File

@ -110,7 +110,8 @@ export default class Patcher {
get: () => patch.originalFunction,
set: undefined
});
} else {
}
else {
patch.module[patch.functionName] = patch.originalFunction;
}
@ -132,9 +133,11 @@ export default class Patcher {
enumerable: true,
...descriptor,
get: () => patch.proxyFunction,
// eslint-disable-next-line no-setter-return
set: value => (patch.originalFunction = value)
});
} else {
}
else {
patch.getter = false;
module[functionName] = patch.proxyFunction;
}
@ -172,7 +175,7 @@ export default class Patcher {
*
* @callback module:Patcher~patchCallback
* @param {object} thisObject - `this` in the context of the original function.
* @param {arguments} arguments - The original arguments of the original function.
* @param {args} args - The original arguments of the original function.
* @param {(function|*)} extraValue - For `instead` patches, this is the original function from the module. For `after` patches, this is the return value of the function.
* @return {*} Makes sense only when using an `instead` or `after` patch. If something other than `undefined` is returned, the returned value replaces the value of `returnValue`. If used for `before` the return value is ignored.
*/

View File

@ -107,11 +107,11 @@ export default new class PluginManager extends AddonManager {
}
catch (error) {
this.state[addon.id] = false;
return new AddonError(addon.name, addon.filename, "load() could not be fired.", {message: error.message, stack: error.stack}, this.prefix);
return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "load()"}), {message: error.message, stack: error.stack}, this.prefix);
}
}
catch (error) {
return new AddonError(addon.name, addon.filename, "Could not be constructed.", {message: error.message, stack: error.stack}, this.prefix);
return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "Plugin constructor()"}), {message: error.message, stack: error.stack}, this.prefix);
}
}
@ -130,7 +130,7 @@ export default new class PluginManager extends AddonManager {
return addon;
}
catch (err) {
throw new AddonError(addon.name || addon.filename, module.filename, "Plugin could not be compiled", {message: err.message, stack: err.stack}, this.prefix);
throw new AddonError(addon.name || addon.filename, module.filename, Strings.Addons.compileError, {message: err.message, stack: err.stack}, this.prefix);
}
}

View File

@ -46,7 +46,7 @@ const reducer = (acc, addon) => {
export default class Updater {
static initialize() {
Settings.registerPanel("updates", "Updates", {
Settings.registerPanel("updates", Strings.Panels.updates, {
order: 1,
element: () => {
return React.createElement(UpdaterPanel, {
@ -90,9 +90,9 @@ export class CoreUpdater {
this.remoteVersion = remoteVersion;
if (!this.hasUpdate || !showNotice) return;
const close = Notices.info(`BetterDiscord has a new update (v${remoteVersion})`, {
const close = Notices.info(Strings.Updater.updateAvailable.format({version: remoteVersion}), {
buttons: [{
label: "More Info",
label: Strings.Notices.moreInfo,
onClick: () => {
close();
UserSettingsWindow?.open?.("updates");
@ -118,7 +118,7 @@ export class CoreUpdater {
this.hasUpdate = false;
Config.version = this.remoteVersion;
Modals.showConfirmationModal("Update Successful!", "BetterDiscord updated successfully. Discord needs to restart in order for it to take effect. Do you want to do this now?", {
Modals.showConfirmationModal(Strings.Updater.updateSuccessful, Strings.Modals.restartPrompt, {
confirmText: Strings.Modals.restartNow,
cancelText: Strings.Modals.restartLater,
danger: true,
@ -127,7 +127,7 @@ export class CoreUpdater {
}
catch (err) {
Logger.stacktrace("Updater", "Failed to update", err);
Modals.showConfirmationModal("Update Failed", "BetterDiscord failed to update. Please download the latest version of the installer from GitHub (https://github.com/BetterDiscord/Installer/releases/latest) and reinstall.", {
Modals.showConfirmationModal(Strings.Updater.updateFailed, Strings.Updater.updateFailedMessage, {
cancelText: null
});
}
@ -191,7 +191,7 @@ class AddonUpdater {
const file = path.join(path.resolve(this.manager.addonFolder), filename);
fileSystem.writeFile(file, body.toString(), () => {
Toasts.success(`${info.name} has been updated to version ${info.version}!`);
Toasts.success(Strings.Updater.addonUpdated.format({name: info.name, version: info.version}));
this.pending.splice(this.pending.indexOf(filename), 1);
});
});
@ -199,9 +199,9 @@ class AddonUpdater {
showUpdateNotice() {
if (!this.pending.length) return;
const close = Notices.info(`BetterDiscord has found updates for ${this.pending.length} of your ${this.type}s!`, {
const close = Notices.info(Strings.Updater.addonUpdatesAvailable.format({count: this.pending.length, type: this.type}), {
buttons: [{
label: "More Info",
label: Strings.Notices.moreInfo,
onClick: () => {
close();
UserSettingsWindow?.open?.("updates");

View File

@ -83,7 +83,12 @@ export default class Modals {
for (const button of buttons) {
const buttonEl = Object.assign(document.createElement("button"), {
onclick: (e) => {
try {button.action(e);} catch (error) {console.error(error);}
try {
button.action(e);
}
catch (error) {
Logger.stacktrace("Modals", "Could not fire button listener", error);
}
handleClose();
},
@ -91,7 +96,7 @@ export default class Modals {
className: "bd-button"
});
if (button.danger) buttonEl.classList.add("bd-button-danger")
if (button.danger) buttonEl.classList.add("bd-button-danger");
buttonEl.append(button.label);
buttonContainer.appendChild(buttonEl);
@ -102,14 +107,16 @@ export default class Modals {
try {
ReactDOM.render(content, container);
} catch (error) {
}
catch (error) {
container.append(DOMManager.parseHTML(`<span style="color: red">There was an unexpected error. Modal could not be rendered.</span>`));
}
DOMManager.onRemoved(container, () => {
ReactDOM.unmountComponentAtNode(container);
});
} else {
}
else {
modal.querySelector(".scroller").append(content);
}
@ -120,7 +127,8 @@ export default class Modals {
if (this.hasModalOpen) {
this.ModalQueue.push(handleOpen);
} else {
}
else {
handleOpen();
}
}
@ -152,15 +160,17 @@ export default class Modals {
const emptyFunction = () => {};
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) return this.default(title, content, [
confirmText && {label: confirmText, action: onConfirm},
cancelText && {label: cancelText, action: onCancel, danger}
].filter(Boolean));
if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) {
return this.default(title, content, [
confirmText && {label: confirmText, action: onConfirm},
cancelText && {label: cancelText, action: onCancel, danger}
].filter(Boolean));
}
if (!Array.isArray(content)) content = [content];
content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c);
let modalKey = ModalActions.openModal(props => {
const modalKey = ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, {
onError: () => {
setTimeout(() => {

View File

@ -1,7 +1,5 @@
import {React} from "modules";
import Drawer from "./drawer";
import Title from "./title";
import Divider from "../divider";
import Switch from "./components/switch";
import Dropdown from "./components/dropdown";
import Number from "./components/number";

View File

@ -1,5 +1,5 @@
import {Config} from "data";
import {React, Events} from "modules";
import {React, Events, Strings} from "modules";
import Drawer from "./settings/drawer";
import SettingItem from "./settings/components/item";
import SettingsTitle from "./settings/title";
@ -10,9 +10,9 @@ import Checkmark from "./icons/check";
class CoreUpdaterPanel extends React.Component {
render() {
return <Drawer name="BetterDiscord" collapsible={true}>
<SettingItem name={`Core v${Config.version}`} note={this.props.hasUpdate ? `Version ${this.props.remoteVersion} now available!` : "No updates available."} inline={true} id={"core-updater"}>
<SettingItem name={`Core v${Config.version}`} note={this.props.hasUpdate ? Strings.Updater.versionAvailable.format({version: this.props.remoteVersion}) : Strings.Updater.noUpdatesAvailable} inline={true} id={"core-updater"}>
{!this.props.hasUpdate && <div className="bd-filled-checkmark"><Checkmark /></div>}
{this.props.hasUpdate && <button className="bd-button">Update!</button>}
{this.props.hasUpdate && <button className="bd-button">{Strings.Updater.updateButton}</button>}
</SettingItem>
</Drawer>;
}
@ -22,7 +22,7 @@ class NoUpdates extends React.Component {
render() {
return <div className="bd-empty-updates">
<Checkmark size="48px" />
{`All of your ${this.props.type} seem to be up to date!`}
{Strings.Updater.upToDateBlankslate.format({type: this.props.type})}
</div>;
}
}
@ -30,13 +30,13 @@ class NoUpdates extends React.Component {
class AddonUpdaterPanel extends React.Component {
render() {
const filenames = this.props.pending;
return <Drawer name={this.props.type} collapsible={true} button={filenames.length ? {title: "Update All!", onClick: () => this.props.updateAll(this.props.type)} : null}>
return <Drawer name={Strings.Panels[this.props.type]} collapsible={true} button={filenames.length ? {title: Strings.Updater.updateAll, onClick: () => this.props.updateAll(this.props.type)} : null}>
{!filenames.length && <NoUpdates type={this.props.type} />}
{filenames.map(f => {
const info = this.props.updater.cache[f];
const addon = this.props.updater.manager.addonList.find(a => a.filename === f);
return <SettingItem name={`${addon.name} v${addon.version}`} note={`Version ${info.version} now available!`} inline={true} id={addon.name}>
<button className="bd-button" onClick={() => this.props.update(this.props.type, f)}>Update!</button>
return <SettingItem name={`${addon.name} v${addon.version}`} note={Strings.Updater.versionAvailable.format({version: info.version})} inline={true} id={addon.name}>
<button className="bd-button" onClick={() => this.props.update(this.props.type, f)}>{Strings.Updater.updateButton}</button>
</SettingItem>;
})}
</Drawer>;
@ -79,11 +79,11 @@ export default class UpdaterPanel extends React.Component {
}
async checkForUpdates() {
Toasts.info("Checking for updates!");
Toasts.info(Strings.Updater.checking);
await this.checkCoreUpdate();
await this.checkAddons("plugins");
await this.checkAddons("themes");
Toasts.info("Finished checking for updates!");
Toasts.info(Strings.Updater.finishedChecking);
}
async checkCoreUpdate() {
@ -120,7 +120,7 @@ export default class UpdaterPanel extends React.Component {
render() {
return [
<SettingsTitle text="Updates" button={{title: "Check For Updates!", onClick: this.checkForUpdates}} />,
<SettingsTitle text={Strings.Panels.updates} button={{title: Strings.Updater.checkForUpdates, onClick: this.checkForUpdates}} />,
<CoreUpdaterPanel remoteVersion={this.props.coreUpdater.remoteVersion} hasUpdate={this.state.hasCoreUpdate} />,
<AddonUpdaterPanel type="plugins" pending={this.state.plugins} update={this.updateAddon} updateAll={this.updateAllAddons} updater={this.props.pluginUpdater} />,
<AddonUpdaterPanel type="themes" pending={this.state.themes} update={this.updateAddon} updateAll={this.updateAllAddons} updater={this.props.themeUpdater} />,