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": { "Panels": {
"updates": "Updates",
"plugins": "Plugins", "plugins": "Plugins",
"themes": "Themes", "themes": "Themes",
"customcss": "Custom CSS" "customcss": "Custom CSS"
@ -237,6 +238,7 @@
"metaNotFound": "META was not found.", "metaNotFound": "META was not found.",
"compileError": "Could not be compiled. See console for details.", "compileError": "Could not be compiled. See console for details.",
"wasUnloaded": "{{name}} was unloaded.", "wasUnloaded": "{{name}} was unloaded.",
"wasLoaded": "{{name}} v{{version}} was loaded.",
"blankSlateHeader": "You don't have any {{type}}s!", "blankSlateHeader": "You don't have any {{type}}s!",
"blankSlateMessage": "Grab some from [this website]({{link}}) and add them to your {{type}} folder." "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." "failureMessage": "BetterDiscord failed to download emotes, please check your internet connection and firewall."
}, },
"PublicServers": { "PublicServers": {
"button": "public", "button": "Public Servers",
"join": "Join", "join": "Join",
"joining": "Joining", "joining": "Joining",
"joined": "Joined", "joined": "Joined",
@ -295,6 +297,9 @@
"additionalInfo": "Additional Info", "additionalInfo": "Additional Info",
"restartPrompt": "In order to take effect, Discord needs to be restarted. Do you want to restart now?" "restartPrompt": "In order to take effect, Discord needs to be restarted. Do you want to restart now?"
}, },
"Notices": {
"moreInfo": "More Info"
},
"ReactDevTools": { "ReactDevTools": {
"notFound": "Extension Not Found", "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." "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", "ascending": "Ascending",
"descending": "Descending" "descending": "Descending"
}, },
"Startup": { "Updater": {
"notSupported": "Not Supported", "updateFailed": "Update Failed!",
"incompatibleApp": "BetterDiscord does not work with {{app}}. Please uninstall one of them.", "updateFailedMessage": "BetterDiscord failed to update. Please download the latest version of the installer from our website (https://betterdiscord.app/) and reinstall.",
"updateNow": "Update Now", "updateSuccessful": "Update Successful!",
"maybeLater": "Maybe Later", "updateAvailable": "BetterDiscord has a new update (v{{version}})",
"updateAvailable": "Update Available", "addonUpdatesAvailable": "BetterDiscord has found updates for {{count}} of your {{.type}}s!",
"updateInfo": "There is an update available for BetterDiscord's Injector ({{version}}).\n\nYou can either update and restart now, or later.", "addonUpdated": "{{name}} has been updated to version {{version}}!",
"updateFailed": "Could Not Update", "checking": "Checking for updates!",
"manualUpdate": "Unable to update automatically, please download the installer and reinstall normally.\n\n[Download Installer](https://github.com/rauenzi/BetterDiscordApp/releases/latest)", "finishedChecking": "Finished checking for updates!",
"jqueryFailed": "jQuery Failed To Load", "checkForUpdates": "Check For Updates!",
"jqueryFailedDetails": "jQuery could not be loaded, and some plugins may not work properly. Proceed at your own risk." "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": { "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?", "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(); req.end();
} };
const request = function (url, options, callback) { const request = function (url, options, callback) {
let responseObject = undefined; let responseObject = null;
let reqObject = null; let reqObject = null;
let pipe = null; let pipe = null;
@ -56,7 +56,8 @@ const request = function (url, options, callback) {
pipe(fsStream) { pipe(fsStream) {
if (!responseObject) { if (!responseObject) {
pipe = fsStream; pipe = fsStream;
} else { }
else {
responseObject.pipe(fsStream); responseObject.pipe(fsStream);
} }
} }

View File

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

View File

@ -60,7 +60,7 @@ export default new class PublicServers extends Builtin {
{ {
id: "public-servers-button", id: "public-servers-button",
onClick: () => this.openPublicServers(), onClick: () => this.openPublicServers(),
text: "Public Servers", text: Strings.PublicServers.button,
icon: () => React.createElement(Globe, {color: "currentColor"}) icon: () => React.createElement(Globe, {color: "currentColor"})
} }
) )
@ -93,8 +93,10 @@ export default new class PublicServers extends Builtin {
aSlot.dataset.listItemId = "public-servers"; aSlot.dataset.listItemId = "public-servers";
// Remove any badges // Remove any badges
const badge = newButton.querySelector(`[class*="premiumTrial"]`); const premiumBadge = newButton.querySelector(`[class*="premiumTrial"]`);
badge?.remove?.(); premiumBadge?.remove?.();
const numberBadge = newButton.querySelector(`[class*="numberBadge-"]`);
numberBadge?.remove?.();
// Render our icon in the avatar slot // Render our icon in the avatar slot
const avatarSlot = newButton.querySelector(`[class*="avatar-"]`); const avatarSlot = newButton.querySelector(`[class*="avatar-"]`);
@ -104,15 +106,18 @@ export default new class PublicServers extends Builtin {
// Replace the existing name // Replace the existing name
const nameSlot = newButton.querySelector(`[class*="name-"]`); const nameSlot = newButton.querySelector(`[class*="name-"]`);
nameSlot.textContent = "Public Servers"; nameSlot.textContent = Strings.PublicServers.button;
// Insert before the header, end of the list // Insert before the header, end of the list
header.parentNode.insertBefore(newButton, header); header.parentNode.insertBefore(newButton, header);
this.button = newButton;
} }
disabled() { disabled() {
this.unpatchAll(); this.unpatchAll();
// DOM.query("#bd-pub-li").remove(); this.button?.remove?.();
document.querySelector("#public-servers-button")?.parentElement?.parentElement?.remove?.();
} }
async _appendButton() { async _appendButton() {
@ -130,12 +135,4 @@ export default new class PublicServers extends Builtin {
openPublicServers() { openPublicServers() {
LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer})); 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; 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); this.emit("loaded", addon);
if (!this.state[addon.id]) return this.state[addon.id] = false; 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.addonList.splice(this.addonList.indexOf(addon), 1);
this.emit("unloaded", addon); 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; return true;
} }

View File

@ -30,7 +30,7 @@ const MenuComponents = (() => {
const ContextMenuActions = (() => { const ContextMenuActions = (() => {
const out = {}; 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)) { for (const key of Object.keys(ActionsModule)) {
if (ActionsModule[key].toString().includes("CONTEXT_MENU_CLOSE")) { if (ActionsModule[key].toString().includes("CONTEXT_MENU_CLOSE")) {
@ -51,10 +51,10 @@ class MenuPatcher {
static initialize() { static initialize() {
const {module, key} = (() => { 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 foundModule = 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 foundKey = Object.keys(foundModule).find(k => foundModule[k].length === 3);
return {module, key}; return {module: foundModule, key: foundKey};
})(); })();
Patcher.before("ContextMenuPatcher", module, key, (_, methodArguments) => { Patcher.before("ContextMenuPatcher", module, key, (_, methodArguments) => {

View File

@ -110,7 +110,8 @@ export default class Patcher {
get: () => patch.originalFunction, get: () => patch.originalFunction,
set: undefined set: undefined
}); });
} else { }
else {
patch.module[patch.functionName] = patch.originalFunction; patch.module[patch.functionName] = patch.originalFunction;
} }
@ -132,9 +133,11 @@ export default class Patcher {
enumerable: true, enumerable: true,
...descriptor, ...descriptor,
get: () => patch.proxyFunction, get: () => patch.proxyFunction,
// eslint-disable-next-line no-setter-return
set: value => (patch.originalFunction = value) set: value => (patch.originalFunction = value)
}); });
} else { }
else {
patch.getter = false; patch.getter = false;
module[functionName] = patch.proxyFunction; module[functionName] = patch.proxyFunction;
} }
@ -172,7 +175,7 @@ export default class Patcher {
* *
* @callback module:Patcher~patchCallback * @callback module:Patcher~patchCallback
* @param {object} thisObject - `this` in the context of the original function. * @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. * @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. * @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) { catch (error) {
this.state[addon.id] = false; 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) { 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; return addon;
} }
catch (err) { 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 { export default class Updater {
static initialize() { static initialize() {
Settings.registerPanel("updates", "Updates", { Settings.registerPanel("updates", Strings.Panels.updates, {
order: 1, order: 1,
element: () => { element: () => {
return React.createElement(UpdaterPanel, { return React.createElement(UpdaterPanel, {
@ -90,9 +90,9 @@ export class CoreUpdater {
this.remoteVersion = remoteVersion; this.remoteVersion = remoteVersion;
if (!this.hasUpdate || !showNotice) return; 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: [{ buttons: [{
label: "More Info", label: Strings.Notices.moreInfo,
onClick: () => { onClick: () => {
close(); close();
UserSettingsWindow?.open?.("updates"); UserSettingsWindow?.open?.("updates");
@ -118,7 +118,7 @@ export class CoreUpdater {
this.hasUpdate = false; this.hasUpdate = false;
Config.version = this.remoteVersion; 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, confirmText: Strings.Modals.restartNow,
cancelText: Strings.Modals.restartLater, cancelText: Strings.Modals.restartLater,
danger: true, danger: true,
@ -127,7 +127,7 @@ export class CoreUpdater {
} }
catch (err) { catch (err) {
Logger.stacktrace("Updater", "Failed to update", 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 cancelText: null
}); });
} }
@ -191,7 +191,7 @@ class AddonUpdater {
const file = path.join(path.resolve(this.manager.addonFolder), filename); const file = path.join(path.resolve(this.manager.addonFolder), filename);
fileSystem.writeFile(file, body.toString(), () => { 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); this.pending.splice(this.pending.indexOf(filename), 1);
}); });
}); });
@ -199,9 +199,9 @@ class AddonUpdater {
showUpdateNotice() { showUpdateNotice() {
if (!this.pending.length) return; 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: [{ buttons: [{
label: "More Info", label: Strings.Notices.moreInfo,
onClick: () => { onClick: () => {
close(); close();
UserSettingsWindow?.open?.("updates"); UserSettingsWindow?.open?.("updates");

View File

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

View File

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

View File

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