diff --git a/package.json b/package.json index f8ad616..dea7ac2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "dependencies": { "@swc/helpers": "0.5.0", "fuzzysort": "^2.0.4", + "intl-messageformat": "^10.5.11", "spitroast": "^1.4.3" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c162e22..692a1cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: fuzzysort: specifier: ^2.0.4 version: 2.0.4 + intl-messageformat: + specifier: ^10.5.11 + version: 10.5.11 spitroast: specifier: ^1.4.3 version: 1.4.3 @@ -250,6 +253,40 @@ packages: dev: true optional: true + /@formatjs/ecma402-abstract@1.18.2: + resolution: {integrity: sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==} + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.2 + dev: false + + /@formatjs/fast-memoize@2.2.0: + resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + dependencies: + tslib: 2.6.2 + dev: false + + /@formatjs/icu-messageformat-parser@2.7.6: + resolution: {integrity: sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==} + dependencies: + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/icu-skeleton-parser': 1.8.0 + tslib: 2.6.2 + dev: false + + /@formatjs/icu-skeleton-parser@1.8.0: + resolution: {integrity: sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==} + dependencies: + '@formatjs/ecma402-abstract': 1.18.2 + tslib: 2.6.2 + dev: false + + /@formatjs/intl-localematcher@0.5.4: + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + dependencies: + tslib: 2.6.2 + dev: false + /@react-native-clipboard/clipboard@1.10.0: resolution: {integrity: sha512-1L+I0vmeUJgMi8MnNsqI00391/RFLkmmxj9qAuOS2madpvce/oNqJb8Pwk2Fc/uxIJSxOckTpq+dQwyPU6s+7w==} peerDependencies: @@ -495,6 +532,15 @@ packages: resolution: {integrity: sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==} dev: false + /intl-messageformat@10.5.11: + resolution: {integrity: sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==} + dependencies: + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/fast-memoize': 2.2.0 + '@formatjs/icu-messageformat-parser': 2.7.6 + tslib: 2.6.2 + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: diff --git a/src/core/commands/plugins.ts b/src/core/commands/plugins.ts index d7a8c7c..8db8faa 100644 --- a/src/core/commands/plugins.ts +++ b/src/core/commands/plugins.ts @@ -5,7 +5,7 @@ import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/comm export default () => { name: "plugins", - description: Strings.COMMAND_DEBUG_DESC, + description: Strings.COMMAND_PLUGINS_DESC, options: [ { name: "ephemeral", diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index b6a56b6..ce2c3e3 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -20,7 +20,7 @@ "COMMAND_EVAL_OPT_CODE": "The code to evaluate.", "COMMAND_PLUGINS_DESC": "Send list of installed plugins.", "COMMAND_PLUGINS_OPT_EPHEMERALLY": "Send plugins list ephemerally.", - "CONFIRMATION_LINK_IS_A_TYPE": "This link is a {urlType}, would you like to install it?", + "CONFIRMATION_LINK_IS_A_TYPE": "This link is a **{urlType, select, plugin {Plugin} theme {Theme} other {Add-on}}**, would you like to install it?", "CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug websocket", "CONNECT_TO_REACT_DEVTOOLS": "Connect to React DevTools", "COPY_URL": "Copy URL", diff --git a/src/core/i18n/index.ts b/src/core/i18n/index.ts index 4bcf3a9..b0c5ce2 100644 --- a/src/core/i18n/index.ts +++ b/src/core/i18n/index.ts @@ -1,5 +1,8 @@ +import { findByName } from "@/lib/metro/filters"; import langDefault from "./default.json"; +const IntlMessageFormat = findByName("MessageFormat") as typeof import("intl-messageformat").default; + type I18nKey = keyof typeof langDefault; export const Strings = new Proxy({}, { @@ -8,26 +11,7 @@ export const Strings = new Proxy({}, { } }) as Record; -export function formatString(key: I18nKey, val: Record) { +export function formatString(key: I18nKey, val: Record) { const str = Strings[key]; - - return str.replaceAll(/{(.*)}/g, (_, cap) => { - return val[cap] ?? cap; - }); -} - -export function formatStringSplit(key: I18nKey, val: Record) { - 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; + return new IntlMessageFormat(str).format(val); } \ No newline at end of file diff --git a/src/core/plugins/quickInstall/forumPost.tsx b/src/core/plugins/quickInstall/forumPost.tsx index dd75e1c..401601e 100644 --- a/src/core/plugins/quickInstall/forumPost.tsx +++ b/src/core/plugins/quickInstall/forumPost.tsx @@ -8,7 +8,7 @@ import { getAssetIDByName } from "@/lib/api/assets"; import { showToast } from "@ui/toasts"; import { useProxy } from "@/lib/api/storage"; import { isThemeSupported } from "@/lib/api/native/loader"; -import { Strings, formatStringSplit } from "@/core/i18n"; +import { Strings, formatString } from "@/core/i18n"; type PostType = "Plugin" | "Theme"; @@ -95,7 +95,7 @@ const actionSheetPatch = () => after("default", ForumPostLongPressActionSheet, ( actions.unshift( } - label={formatStringSplit(installed ? "UNINSTALL_TITLE" : "INSTALL_TITLE", { title: postType })} + label={formatString(installed ? "UNINSTALL_TITLE" : "INSTALL_TITLE", { title: postType })} disabled={loading} onPress={installOrRemove} /> diff --git a/src/core/plugins/quickInstall/url.tsx b/src/core/plugins/quickInstall/url.tsx index ff2fe59..3e1ea94 100644 --- a/src/core/plugins/quickInstall/url.tsx +++ b/src/core/plugins/quickInstall/url.tsx @@ -8,7 +8,7 @@ import { showConfirmationAlert } from "@ui/alerts"; import { getAssetIDByName } from "@/lib/api/assets"; import { showToast } from "@ui/toasts"; import { isThemeSupported } from "@/lib/api/native/loader"; -import { Strings, formatString, formatStringSplit } from "@/core/i18n"; +import { Strings, formatString } from "@/core/i18n"; const showSimpleActionSheet = find((m) => m?.showSimpleActionSheet && !Object.getOwnPropertyDescriptor(m, "showSimpleActionSheet")?.get); const handleClick = findByProps("handleClick"); @@ -16,18 +16,16 @@ const { openURL } = url; const { getChannelId } = channels; const { getChannel } = findByProps("getChannel"); -const { TextStyleSheet } = findByProps("TextStyleSheet"); - function typeFromUrl(url: string) { if (url.startsWith(PROXY_PREFIX)) { - return "Plugin"; + return "plugin"; } else if (url.endsWith(".json") && isThemeSupported()) { - return "Theme"; - } else return; + return "theme"; + } } -function installWithToast(type: "Plugin" | "Theme", url: string) { - (type === "Plugin" ? installPlugin : installTheme)(url) +function installWithToast(type: "plugin" | "theme", url: string) { + (type === "plugin" ? installPlugin : installTheme)(url) .then(() => { showToast(Strings.TOASTS_SUCCESSFULLY_INSTALLED, getAssetIDByName("Check")); }) @@ -65,11 +63,11 @@ export default () => { if (!urlType) return orig.apply(this, args); // Make clicking on theme links only work in #themes, should there be a theme proxy in the future, this can be removed. - if (urlType === "Theme" && getChannel(getChannelId())?.parent_id !== THEMES_CHANNEL_ID) return orig.apply(this, args); + if (urlType === "theme" && getChannel(getChannelId())?.parent_id !== THEMES_CHANNEL_ID) return orig.apply(this, args); showConfirmationAlert({ title: Strings.HOLD_UP, - content: formatStringSplit("CONFIRMATION_LINK_IS_A_TYPE", { urlType: {urlType} }), + content: formatString("CONFIRMATION_LINK_IS_A_TYPE", { urlType }), onConfirm: () => installWithToast(urlType, url), confirmText: Strings.INSTALL, cancelText: Strings.CANCEL,