i18n: bad implementation

This commit is contained in:
amsyarasyiq 2024-02-27 17:22:23 +08:00
parent cef960bc2d
commit 2ff46796bd
19 changed files with 212 additions and 96 deletions

View file

@ -65,7 +65,7 @@ try {
console.log(`swc plugin: time-string="${timeString}"`);
})
build.onLoad({ filter: /\.[jt]sx?/ }, async args => {
build.onLoad({ filter: /\.[jt]sx?$/ }, async args => {
const result = await swc.transformFile(args.path, {
jsc: {
externalHelpers: true,

70
src/lib/i18n/default.json Normal file
View file

@ -0,0 +1,70 @@
{
"ACTIONS": "Actions",
"ARE_YOU_SURE_TO_CLEAR_DATA": "Are you sure you wish to clear the data of {name}?",
"ARE_YOU_SURE_TO_DELETE_PLUGIN": "Are you sure you wish to delete {name}? This will clear all of the plugin's data.",
"ARE_YOU_SURE_TO_DELETE_THEME": "Are you sure you wish to delete {name}?",
"BRAND": "Brand",
"BUNNY": "Bunny",
"BUNNY_URL": "Bunny URL",
"BYTECODE": "Bytecode",
"CANCEL": "Cancel",
"CLEAR": "Clear",
"CLEAR_DATA": "Clear data",
"CLEAR_DATA_FAILED": "Failed to clear data for {name}!",
"CLEAR_DATA_SUCCESSFUL": "Cleared data for {name}.",
"CODENAME": "Codename",
"CONFIRMATION_LINK_IS_A_TYPE": "This link is a {urlType}, would you like to install it?",
"COPY_URL": "Copy URL",
"DELETE": "Delete",
"DEVELOPER": "Developer",
"DEVELOPER_SETTINGS": "Developer Settings",
"DISABLE_THEME": "Disable Theme",
"DISABLE_UPDATES": "Disable updates",
"DISCORD_SERVER": "Discord Server",
"ENABLE_UPDATES": "Enable updates",
"GENERAL": "General",
"GITHUB": "GitHub",
"HOLD_UP": "Hold Up",
"INFO": "Info",
"INSTALL": "Install",
"INSTALL_PLUGIN": "Install Plugin",
"INSTALL_THEME": "Install Theme",
"INSTALL_TITLE": "Install {title}",
"LINKS": "Links",
"LOADER": "Loader",
"MACHINE_ID": "Machine ID",
"MANUFACTURER": "Manufacturer",
"MODAL_THEME_REFETCHED": "Theme refetched",
"MODAL_THEME_REFETCHED_DESC": "A reload is required to see the changes. Do you want to reload now?",
"MODAL_UNPROXIED_PLUGIN_DESC": "The plugin you are trying to install has not been proxied/verified by Vendetta staff. Are you sure you want to continue?",
"MODAL_UNPROXIED_PLUGIN_HEADER": "Unproxied Plugin",
"OPEN_IN_BROWSER": "Open in Browser",
"OPERATING_SYSTEM": "Operating System",
"OVERFLOW_PLUGIN_SETTINGS": "Plugin settings",
"PHONE_MODEL": "Model",
"PLATFORM": "Platform",
"PLUGINS": "Plugins",
"REFETCH": "Refetch",
"RELOAD": "Reload",
"RELOAD_DISCORD": "Reload Discord",
"RELOAD_IN_NORMAL_MODE": "Reload in Normal Mode",
"RELOAD_IN_NORMAL_MODE_DESC": "This will reload Discord normally",
"RELOAD_IN_SAFE_MODE": "Reload in Safe Mode",
"RELOAD_IN_SAFE_MODE_DESC": "This will reload Discord without loading addons",
"RETRY": "Retry",
"SAFE_MODE": "Safe Mode",
"SAFE_MODE_NOTICE_PLUGINS": "You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.",
"SAFE_MODE_NOTICE_THEMES": "You are in Safe Mode, meaning themes have been temporarily disabled.",
"SAFE_MODE_NOTICE_THEMES_EXTEND": "If a theme appears to be causing the issue, you can press below to disable it persistently.",
"SEARCH": "Search",
"THEME_REFETCH_FAILED": "Failed to refetch theme!",
"THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme.",
"THEMES": "Themes",
"THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes!",
"TOASTS_INSTALLED_PLUGIN": "Installed plugin",
"TOASTS_SUCCESSFULLY_INSTALLED": "Successfully installed",
"UH_OH": "Uh oh.",
"UNINSTALL": "Uninstall",
"UNINSTALL_TITLE": "Uninstall {title}",
"VERSIONS": "Versions"
}

33
src/lib/i18n/index.ts Normal file
View file

@ -0,0 +1,33 @@
import langDefault from "./default.json";
type I18nKey = keyof typeof langDefault;
export const Strings = new Proxy({}, {
get: (_t, prop: keyof typeof langDefault) => {
return langDefault[prop];
}
}) as Record<I18nKey, string>;
export function formatString(key: I18nKey, val: Record<string, any>) {
const str = Strings[key];
return str.replaceAll(/{(.*)}/g, (_, cap) => {
return val[cap] ?? cap;
});
}
export function formatStringSplit(key: I18nKey, val: Record<string, any>) {
const str = Strings[key];
const splitted = str.split(/({[^}]+})/);
for (const inp in val) {
for (let i = 0; i < splitted.length; i++) {
if (splitted[i] === `{${inp}}`) {
splitted[i] = val[inp];
}
}
}
return splitted;
}

View file

@ -1,3 +1,4 @@
import { Strings } from "@/lib/i18n";
import { React, ReactNative as RN, stylesheet } from "@metro/common";
import { Forms, Button, Codeblock } from "@ui/components";
@ -36,14 +37,14 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
return (
<RN.ScrollView style={styles.view}>
<Forms.FormText style={styles.title}>Uh oh.</Forms.FormText>
<Forms.FormText style={styles.title}>{Strings.UH_OH}</Forms.FormText>
<Codeblock selectable style={{ marginBottom: 5 }}>{this.state.errText}</Codeblock>
<Button
color={Button.Colors.RED}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onPress={() => this.setState({ hasErr: false, errText: undefined })}
text="Retry"
text={Strings.RETRY}
/>
</RN.ScrollView>
)

View file

@ -2,6 +2,7 @@ import { ReactNative as RN } from "@metro/common";
import { findByProps } from "@metro/filters";
import ErrorBoundary from "@ui/components/ErrorBoundary";
import { getAssetIDByName } from "@ui/assets";
import { Strings } from "@/lib/i18n";
const { TextInput } = findByProps("TableRow");
@ -28,7 +29,7 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
<RN.View style={style}>
<TextInput isClearable grow
leadingIcon={SearchIcon}
placeholder={placeholder ?? "Search"}
placeholder={placeholder ?? Strings.SEARCH}
onChange={onChange}
returnKeyType={"search"}
size="md"

View file

@ -8,6 +8,7 @@ import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts";
import { useProxy } from "@/lib/storage";
import { isThemeSupported } from "@/lib/loader";
import { Strings, formatStringSplit } from "@/lib/i18n";
type PostType = "Plugin" | "Theme";
@ -94,7 +95,7 @@ const actionSheetPatch = () => after("default", ForumPostLongPressActionSheet, (
actions.unshift(<ActionsSection key="install">
<ActionSheetRow
icon={<ActionSheetRow.Icon source={getAssetIDByName(installed ? "ic_message_delete" : "DownloadIcon")} />}
label={`${installed ? "Uninstall" : "Install"} ${postType}`}
label={formatStringSplit(installed ? "UNINSTALL_TITLE" : "INSTALL_TITLE", { title: postType })}
disabled={loading}
onPress={installOrRemove}
/>
@ -112,7 +113,7 @@ const installButtonPatch = () => after("MostCommonForumPostReaction", forumReact
loading={loading}
disabled={loading}
variant={installed ? "danger" : "primary"}
text={installed ? "Uninstall" : "Install"}
text={installed ? Strings.UNINSTALL : Strings.INSTALL}
onPress={installOrRemove}
icon={getAssetIDByName(installed ? "ic_message_delete" : "DownloadIcon")}
style={{ marginLeft: 8 }}

View file

@ -8,6 +8,7 @@ import { showConfirmationAlert } from "@ui/alerts";
import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts";
import { isThemeSupported } from "@/lib/loader";
import { Strings, formatString, formatStringSplit } from "@lib/i18n";
const showSimpleActionSheet = find((m) => m?.showSimpleActionSheet && !Object.getOwnPropertyDescriptor(m, "showSimpleActionSheet")?.get);
const handleClick = findByProps("handleClick");
@ -28,7 +29,7 @@ function typeFromUrl(url: string) {
function installWithToast(type: "Plugin" | "Theme", url: string) {
(type === "Plugin" ? installPlugin : installTheme)(url)
.then(() => {
showToast("Successfully installed", getAssetIDByName("Check"));
showToast(Strings.TOASTS_SUCCESSFULLY_INSTALLED, getAssetIDByName("Check"));
})
.catch((e: Error) => {
showToast(e.message, getAssetIDByName("Small"));
@ -50,7 +51,7 @@ export default () => {
if (!urlType) return;
options.push({
label: `Install ${urlType}`,
label: formatString("INSTALL_TITLE", { urlType }),
onPress: () => installWithToast(urlType, url),
});
})
@ -67,12 +68,12 @@ export default () => {
if (urlType === "Theme" && getChannel(getChannelId())?.parent_id !== THEMES_CHANNEL_ID) return orig.apply(this, args);
showConfirmationAlert({
title: "Hold Up",
content: ["This link is a ", <RN.Text style={TextStyleSheet["text-md/semibold"]}>{urlType}</RN.Text>, ", would you like to install it?"],
title: Strings.HOLD_UP,
content: formatStringSplit("CONFIRMATION_LINK_IS_A_TYPE", { urlType: <RN.Text style={TextStyleSheet["text-md/semibold"]}>{urlType}</RN.Text> }),
onConfirm: () => installWithToast(urlType, url),
confirmText: "Install",
cancelText: "Cancel",
secondaryConfirmText: "Open in Browser",
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
secondaryConfirmText: Strings.OPEN_IN_BROWSER,
onConfirmSecondary: () => openURL(url),
});
})

View file

@ -7,6 +7,7 @@ import { findByProps } from "@/lib/metro/filters";
import { HTTP_REGEX_MULTI } from "@/lib/constants";
import { showInputAlert } from "@/ui/alerts";
import { getAssetIDByName } from "@/ui/assets";
import { Strings, formatString } from "@/lib/i18n";
interface AddonPageProps<T> {
title: string;
@ -40,7 +41,7 @@ export default function AddonPage<T>({ title, fetchFunction, items, safeModeMess
<Search
style={{ marginBottom: 15 }}
onChangeText={(v: string) => setSearch(v.toLowerCase())}
placeholder="Search"
placeholder={Strings.SEARCH}
/>
</>}
onScroll={e => {
@ -55,19 +56,19 @@ export default function AddonPage<T>({ title, fetchFunction, items, safeModeMess
renderItem={({ item, index }) => <CardComponent item={item} index={index} />}
/>
<FloatingActionButton
text={`Install ${title}`}
text={formatString("INSTALL_TITLE", { title })}
icon={getAssetIDByName("DownloadIcon")}
state={{ collapseText }}
onPress={() => {
// from ./InstallButton.tsx
clipboard.getString().then((content) =>
showInputAlert({
title: `Install ${title}`,
title: formatString("INSTALL_TITLE", { title }),
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
placeholder: "https://example.com/",
onConfirm: (input: string) => fetchFunction(input),
confirmText: "Install",
cancelText: "Cancel",
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
})
)
}}

View file

@ -4,9 +4,7 @@ import { findByProps } from "@metro/filters";
import { getAssetIDByName } from "@ui/assets";
import { semanticColors } from "@ui/color";
const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup, IconButton } = findByProps("TableRow");
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow");
// TODO: These styles work weirdly. iOS has cramped text, Android with low DPI probably does too. Fix?
const styles = stylesheet.createThemedStyleSheet({

View file

@ -3,6 +3,7 @@ import { HTTP_REGEX_MULTI } from "@lib/constants";
import { showInputAlert } from "@ui/alerts";
import { getAssetIDByName } from "@ui/assets";
import { semanticColors } from "@ui/color";
import { Strings } from "@/lib/i18n";
const styles = stylesheet.createThemedStyleSheet({
icon: {
@ -25,8 +26,8 @@ export default function InstallButton({ alertTitle, installFunction: fetchFuncti
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
placeholder: "https://example.com/",
onConfirm: (input: string) => fetchFunction(input),
confirmText: "Install",
cancelText: "Cancel",
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
})
)
}>

View file

@ -7,6 +7,7 @@ import { showConfirmationAlert } from "@ui/alerts";
import Card, { CardWrapper } from "@ui/settings/components/Card";
import { useProxy } from "@/lib/storage";
import { ButtonColors } from "@/lib/types";
import { Strings, formatString } from "@/lib/i18n";
async function stopThenStart(plugin: BunnyPlugin, callback: Function) {
if (plugin.enabled) stopPlugin(plugin.id, false);
@ -43,7 +44,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
overflowTitle={plugin.manifest.name}
overflowActions={[
...(settings ? [{
label: "Plugin settings",
label: Strings.OVERFLOW_PLUGIN_SETTINGS,
icon: "settings",
onPress: () => navigation.push("VendettaCustomPage", {
title: plugin.manifest.name,
@ -52,7 +53,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
}] : []),
{
icon: "ic_sync_24px",
label: "Refetch",
label: Strings.REFETCH,
onPress: async () => {
stopThenStart(plugin, () => {
fetchPlugin(plugin.id).then(async () => {
@ -65,7 +66,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
},
{
icon: "copy",
label: "Copy URL",
label: Strings.COPY_URL,
onPress: () => {
clipboard.setString(plugin.id);
showToast.showCopyToClipboard();
@ -73,7 +74,7 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
},
{
icon: "ic_download_24px",
label: plugin.update ? "Disable updates" : "Enable updates",
label: plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES,
onPress: () => {
plugin.update = !plugin.update;
showToast(`${plugin.update ? "Enabled" : "Disabled"} updates for ${plugin.manifest.name}.`, getAssetIDByName("toast_image_saved"));
@ -81,21 +82,21 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
},
{
icon: "ic_duplicate",
label: "Clear data",
label: Strings.CLEAR_DATA,
isDestructive: true,
onPress: () => showConfirmationAlert({
title: "Wait!",
content: `Are you sure you wish to clear the data of ${plugin.manifest.name}?`,
confirmText: "Clear",
cancelText: "Cancel",
title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_CLEAR_DATA", { name: plugin.manifest.name }),
confirmText: Strings.CLEAR,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => {
stopThenStart(plugin, () => {
try {
MMKVManager.removeItem(plugin.id);
showToast(`Cleared data for ${plugin.manifest.name}.`, getAssetIDByName("trash"));
showToast(formatString("CLEAR_DATA_SUCCESSFUL", { name: plugin.manifest.name }), getAssetIDByName("trash"));
} catch {
showToast(`Failed to clear data for ${plugin.manifest.name}!`, getAssetIDByName("Small"));
showToast(formatString("CLEAR_DATA_FAILED", { name: plugin.manifest.name }), getAssetIDByName("Small"));
}
});
}
@ -103,13 +104,13 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
},
{
icon: "ic_message_delete",
label: "Delete",
label: Strings.DELETE,
isDestructive: true,
onPress: () => showConfirmationAlert({
title: "Wait!",
content: `Are you sure you wish to delete ${plugin.manifest.name}? This will clear all of the plugin's data.`,
confirmText: "Delete",
cancelText: "Cancel",
title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", {name: plugin.manifest.name}),
confirmText: Strings.DELETE,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => {
try {

View file

@ -4,6 +4,7 @@ import { getAssetIDByName } from "@ui/assets";
import { getRenderableScreens } from "@ui/settings/data";
import { ErrorBoundary, Forms } from "@ui/components";
import settings from "@lib/settings";
import { Strings } from "@/lib/i18n";
const { FormRow, FormSection, FormDivider } = Forms;
@ -15,7 +16,7 @@ export default function SettingsSection() {
return (
<ErrorBoundary>
<FormSection key="Vendetta" title={`Bunny${settings.safeMode?.enabled ? " (Safe Mode)" : ""}`}>
<FormSection key="Vendetta" title={`${Strings.BUNNY}${settings.safeMode?.enabled ? ` (${Strings.SAFE_MODE})` : ""}`}>
{screens.map((s, i) => (
<>
<FormRow

View file

@ -8,6 +8,7 @@ import { showToast } from "@ui/toasts";
import settings from "@lib/settings";
import Card, { CardWrapper } from "@ui/settings/components/Card";
import { ButtonColors } from "@/lib/types";
import { Strings, formatString } from "@/lib/i18n";
async function selectAndApply(value: boolean, id: string) {
try {
@ -15,7 +16,7 @@ async function selectAndApply(value: boolean, id: string) {
value ? applyTheme(themes[id]) : applyTheme(null);
// TODO: Implement native side reload-less & check if it's applied by 100%
showToast("Reload the app to fully apply changes!", getAssetIDByName("yellow-alert"));
showToast(Strings.THEMES_RELOAD_FOR_CHANGES, getAssetIDByName("yellow-alert"));
} catch (e: any) {
console.error("Error while selectAndApply,", e)
}
@ -45,29 +46,29 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
overflowActions={[
{
icon: "ic_sync_24px",
label: "Refetch",
label: Strings.REFETCH,
onPress: () => {
fetchTheme(theme.id, theme.selected).then(() => {
if (theme.selected) {
showConfirmationAlert({
title: "Theme refetched",
content: "A reload is required to see the changes. Do you want to reload now?",
confirmText: "Reload",
cancelText: "Cancel",
title: Strings.MODAL_THEME_REFETCHED,
content: Strings.MODAL_THEME_REFETCHED_DESC,
confirmText: Strings.RELOAD,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => BundleUpdaterManager.reload(),
})
} else {
showToast("Successfully refetched theme.", getAssetIDByName("toast_image_saved"));
showToast(Strings.THEME_REFETCH_SUCCESSFUL, getAssetIDByName("toast_image_saved"));
}
}).catch(() => {
showToast("Failed to refetch theme!", getAssetIDByName("Small"));
showToast(Strings.THEME_REFETCH_FAILED, getAssetIDByName("Small"));
});
},
},
{
icon: "copy",
label: "Copy URL",
label: Strings.COPY_URL,
onPress: () => {
clipboard.setString(theme.id);
showToast.showCopyToClipboard();
@ -75,13 +76,13 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
},
{
icon: "ic_message_delete",
label: "Delete",
label: Strings.DELETE,
isDestructive: true,
onPress: () => showConfirmationAlert({
title: "Wait!",
content: `Are you sure you wish to delete ${theme.data.name}?`,
confirmText: "Delete",
cancelText: "Cancel",
title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_DELETE_THEME", { name: theme.data.name }),
confirmText: Strings.DELETE,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => {
removeTheme(theme.id).then((wasSelected) => {

View file

@ -16,6 +16,7 @@ import Developer from "@ui/settings/pages/Developer";
import { PROXY_PREFIX } from "@/lib/constants";
import { findByProps } from "@/lib/metro/filters";
import { isThemeSupported } from "@/lib/loader";
import { Strings } from "@/lib/i18n";
const { useSafeAreaInsets } = findByProps("useSafeAreaInsets");
@ -45,29 +46,29 @@ const keyMap = (screens: Screen[], data: string | ((s: Screen) => any) | null) =
export const getScreens = (youKeys = false): Screen[] => [
{
key: formatKey("VendettaSettings", youKeys),
title: "General",
title: Strings.GENERAL,
icon: "settings",
render: General,
},
{
key: formatKey("VendettaPlugins", youKeys),
title: "Plugins",
title: Strings.PLUGINS,
icon: "debug",
options: {
headerRight: () => (
<InstallButton
alertTitle="Install Plugin"
alertTitle={Strings.INSTALL_PLUGIN}
installFunction={async (input) => {
if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings)
setImmediate(() => showConfirmationAlert({
title: "Unproxied Plugin",
content: "The plugin you are trying to install has not been proxied/verified by Vendetta staff. Are you sure you want to continue?",
confirmText: "Install",
title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER,
content: Strings.MODAL_UNPROXIED_PLUGIN_DESC,
confirmText: Strings.INSTALL,
onConfirm: () =>
installPlugin(input)
.then(() => showToast("Installed plugin", getAssetIDByName("Check")))
.then(() => showToast(Strings.TOASTS_INSTALLED_PLUGIN, getAssetIDByName("Check")))
.catch((x) => showToast(x?.message ?? `${x}`, getAssetIDByName("Small"))),
cancelText: "Cancel",
cancelText: Strings.CANCEL,
}));
else return await installPlugin(input);
}}
@ -78,18 +79,18 @@ export const getScreens = (youKeys = false): Screen[] => [
},
{
key: formatKey("VendettaThemes", youKeys),
title: "Themes",
title: Strings.THEMES,
icon: "ic_theme_24px",
// TODO: bad
shouldRender: () => isThemeSupported(),
options: {
headerRight: () => !settings.safeMode?.enabled && <InstallButton alertTitle="Install Theme" installFunction={installTheme} />,
headerRight: () => !settings.safeMode?.enabled && <InstallButton alertTitle={Strings.INSTALL_THEME} installFunction={installTheme} />,
},
render: Themes,
},
{
key: formatKey("VendettaDeveloper", youKeys),
title: "Developer",
title: Strings.DEVELOPER,
icon: "ic_progress_wrench_24px",
shouldRender: () => settings.developerSettings ?? false,
render: Developer,
@ -120,8 +121,8 @@ export const getYouData = () => {
return {
getLayout: () => ({
title: "Bunny",
label: "Bunny",
title: "Vendetta",
label: Strings.BUNNY,
// We can't use our keyMap function here since `settings` is an array not an object
settings: getRenderableScreens(true).map(s => s.key)
}),

View file

@ -8,6 +8,7 @@ import settings, { loaderConfig } from "@lib/settings";
import AssetBrowser from "@ui/settings/pages/AssetBrowser";
import { semanticColors } from "@ui/color";
import { getReactDevToolsProp, getReactDevToolsVersion, isLoaderConfigSupported, isReactDevToolsPreloaded } from "@/lib/loader";
import { Strings } from "@/lib/i18n";
const { Stack, TableRow, TableSwitchRow, TableRowGroup, TextInput } = findByProps("TableRow");
const { FormText } = Forms;
@ -74,7 +75,7 @@ export default function Developer() {
size="md"
onChange={(v: string) => loaderConfig.customLoadUrl.url = v}
placeholder="http://localhost:4040/vendetta.js"
label="Bunny URL"
label={Strings.BUNNY_URL}
/>} />}
{isReactDevToolsPreloaded() && <TableSwitchRow
label="Load React DevTools"
@ -112,7 +113,7 @@ export default function Developer() {
options: [
// @ts-expect-error
// Of course, to trigger an error, we need to do something incorrectly. The below will do!
{ label: "Bunny", onPress: () => navigation.push("VendettaCustomPage", { render: () => <undefined /> }) },
{ label: Strings.BUNNY, onPress: () => navigation.push("VendettaCustomPage", { render: () => <undefined /> }) },
{ label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) },
],
})}

View file

@ -8,6 +8,7 @@ import settings from "@lib/settings";
import Version from "@ui/settings/components/Version";
import { findByProps } from "@lib/metro/filters";
import { semanticColors } from "@ui/color";
import { Strings } from "@/lib/i18n";
const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow");
const debugInfo = getDebugInfo() as any;
@ -24,7 +25,7 @@ export default function General() {
const versions = [
{
label: "Bunny",
label: Strings.BUNNY,
version: debugInfo.vendetta.version,
icon: "ic_progress_wrench_24px",
},
@ -44,7 +45,7 @@ export default function General() {
icon: "mobile",
},
{
label: "Bytecode",
label: Strings.BYTECODE,
version: debugInfo.hermes.bytecodeVersion,
icon: "ic_server_security_24px",
},
@ -52,12 +53,12 @@ export default function General() {
const platformInfo = [
{
label: "Loader",
label: Strings.LOADER,
version: debugInfo.vendetta.loader,
icon: "ic_download_24px",
},
{
label: "Operating System",
label: Strings.OPERATING_SYSTEM,
version: `${debugInfo.os.name} ${debugInfo.os.version}`,
icon: "ic_cog_24px"
},
@ -67,22 +68,22 @@ export default function General() {
icon: "pencil"
}] : []),
{
label: "Manufacturer",
label: Strings.MANUFACTURER,
version: debugInfo.device.manufacturer,
icon: "ic_badge_staff"
},
{
label: "Brand",
label: Strings.BRAND,
version: debugInfo.device.brand,
icon: "ic_settings_boost_24px"
},
{
label: "Model",
label: Strings.PHONE_MODEL,
version: debugInfo.device.model,
icon: "ic_phonelink_24px"
},
{
label: RN.Platform.select({ android: "Codename", ios: "Machine ID" })!,
label: RN.Platform.select({ android: Strings.CODENAME, ios: Strings.MACHINE_ID })!,
version: debugInfo.device.codename,
icon: "ic_compose_24px"
}
@ -91,34 +92,34 @@ export default function General() {
return (
<RN.ScrollView style={styles.container}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={24}>
<TableRowGroup title="Links">
<TableRowGroup title={Strings.LINKS}>
<TableRow
label="Discord Server"
label={Strings.DISCORD_SERVER}
icon={<TableRow.Icon source={getAssetIDByName("Discord")} />}
trailing={TableRow.Arrow}
onPress={() => url.openDeeplink(DISCORD_SERVER)}
/>
<TableRow
label="GitHub"
label={Strings.GITHUB}
icon={<TableRow.Icon source={getAssetIDByName("img_account_sync_github_white")} />}
trailing={TableRow.Arrow}
onPress={() => url.openURL(GITHUB)}
/>
</TableRowGroup>
<TableRowGroup title="Actions">
<TableRowGroup title={Strings.ACTIONS}>
<TableRow
label="Reload Discord"
label={Strings.RELOAD_DISCORD}
icon={<TableRow.Icon source={getAssetIDByName("ic_message_retry")} />}
onPress={() => RN.NativeModules.BundleUpdaterManager.reload()}
/>
<TableRow
label={settings.safeMode?.enabled ? "Return to Normal Mode" : "Reload in Safe Mode"}
subLabel={`This will reload Discord ${settings.safeMode?.enabled ? "normally." : "without loading addons."}`}
label={settings.safeMode?.enabled ? Strings.RELOAD_IN_NORMAL_MODE : Strings.RELOAD_IN_SAFE_MODE}
subLabel={settings.safeMode?.enabled ? Strings.RELOAD_IN_NORMAL_MODE_DESC : Strings.RELOAD_IN_SAFE_MODE_DESC}
icon={<TableRow.Icon source={getAssetIDByName("ic_privacy_24px")} />}
onPress={toggleSafeMode}
/>
<TableSwitchRow
label="Developer Settings"
label={Strings.DEVELOPER_SETTINGS}
icon={<TableRow.Icon source={getAssetIDByName("ic_progress_wrench_24px")} />}
value={settings.developerSettings}
onValueChange={(v: boolean) => {
@ -126,11 +127,11 @@ export default function General() {
}}
/>
</TableRowGroup>
<TableRowGroup title="Info">
<Summary noPadding label="Versions" icon="ic_information_filled_24px">
<TableRowGroup title={Strings.INFO}>
<Summary noPadding label={Strings.VERSIONS} icon="ic_information_filled_24px">
{versions.map(v => <Version padding label={v.label} version={v.version} icon={v.icon} />)}
</Summary>
<Summary noPadding label="Platform" icon="ic_mobile_device">
<Summary noPadding label={Strings.PLATFORM} icon="ic_mobile_device">
{platformInfo.map(p => <Version padding label={p.label} version={p.version} icon={p.icon} />)}
</Summary>
</TableRowGroup>

View file

@ -3,16 +3,17 @@ import { BunnyPlugin, installPlugin, plugins } from "@lib/plugins";
import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
import PluginCard from "@ui/settings/components/PluginCard";
import { Strings } from "@/lib/i18n";
export default function Plugins() {
useProxy(settings)
return (
<AddonPage<BunnyPlugin>
title="Plugin"
title={Strings.PLUGINS}
fetchFunction={installPlugin}
items={plugins}
safeModeMessage="You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page."
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
card={PluginCard}
/>
)

View file

@ -5,18 +5,19 @@ import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
import ThemeCard from "@ui/settings/components/ThemeCard";
import { ButtonColors } from "@/lib/types";
import { Strings } from "@/lib/i18n";
export default function Themes() {
useProxy(settings);
return (
<AddonPage<Theme>
title={"Theme"}
title={Strings.THEMES}
fetchFunction={installTheme}
items={themes}
safeModeMessage={`You are in Safe Mode, meaning themes have been temporarily disabled.${settings.safeMode?.currentThemeId ? " If a theme appears to be causing the issue, you can press below to disable it persistently." : ""}`}
safeModeMessage={`${Strings.SAFE_MODE_NOTICE_THEMES}${settings.safeMode?.currentThemeId ? ` ${Strings.SAFE_MODE_NOTICE_THEMES_EXTEND}` : ""}`}
safeModeExtras={settings.safeMode?.currentThemeId ? <Button
text="Disable Theme"
text={Strings.DISABLE_THEME}
color={ButtonColors.BRAND}
size="small"
onPress={() => {

View file

@ -2,6 +2,7 @@ import { findByProps } from "@metro/filters";
import { after, before } from "@lib/patcher";
import { getRenderableScreens, getScreens, getYouData } from "@ui/settings/data";
import { i18n } from "@lib/metro/common";
import { Strings } from "@/lib/i18n";
export default function patchYou() {
const patches = new Array<Function>;
@ -44,7 +45,7 @@ function oldYouPatch(patches: Function[]) {
ancestorRendererData: data.rendererConfigs[s.key],
setting: s.key,
title: data.titleConfig[s.key],
breadcrumbs: ["Bunny"],
breadcrumbs: [Strings.BUNNY],
icon: data.rendererConfigs[s.key].icon,
})),
// .filter can be removed when dropping support for 189.3 and below (unless Discord changes things again)
@ -78,7 +79,7 @@ function newYouPatch(patches: Function[]) {
patches.push(before("type", settingsListComponents.SearchableSettingsList, ([{ sections }]) => manipulateSections(sections, data.getLayout())));
patches.push(after("getSettingListSearchResultItems", gettersModule, (_, ret) => {
ret.forEach((s: any) => screens.some(b => b.key === s.setting) && (s.breadcrumbs = ["Bunny"]))
ret.forEach((s: any) => screens.some(b => b.key === s.setting) && (s.breadcrumbs = [Strings.BUNNY]))
}));
const oldRendererConfig = settingConstantsModule.SETTING_RENDERER_CONFIG;
@ -94,7 +95,7 @@ function newYouPatch(patches: Function[]) {
const isLabel = (i: any, name: string) => i?.label === name || i?.title === name;
function manipulateSections(sections: any[], layout: any) {
if (!Array.isArray(sections) || sections.find((i: any) => isLabel(i, "Bunny"))) return;
if (!Array.isArray(sections) || sections.find((i: any) => isLabel(i, Strings.BUNNY))) return;
// Add our settings
const accountSettingsIndex = sections.findIndex((i: any) => isLabel(i, i18n.Messages.ACCOUNT_SETTINGS));