i18n: Use internal formatting

This commit is contained in:
amsyarasyiq 2024-03-05 23:41:45 +08:00
parent eb59c0a0fb
commit 77b78071d7
7 changed files with 64 additions and 35 deletions

View file

@ -34,6 +34,7 @@
"dependencies": {
"@swc/helpers": "0.5.0",
"fuzzysort": "^2.0.4",
"intl-messageformat": "^10.5.11",
"spitroast": "^1.4.3"
},
"pnpm": {

46
pnpm-lock.yaml generated
View file

@ -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:

View file

@ -5,7 +5,7 @@ import { ApplicationCommand, ApplicationCommandOptionType } from "@/lib/api/comm
export default () => <ApplicationCommand>{
name: "plugins",
description: Strings.COMMAND_DEBUG_DESC,
description: Strings.COMMAND_PLUGINS_DESC,
options: [
{
name: "ephemeral",

View file

@ -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",

View file

@ -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<I18nKey, string>;
export function formatString(key: I18nKey, val: Record<string, any>) {
export function formatString<T = void>(key: I18nKey, val: Record<string, T>) {
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;
return new IntlMessageFormat(str).format(val);
}

View file

@ -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(<ActionsSection key="install">
<ActionSheetRow
icon={<ActionSheetRow.Icon source={getAssetIDByName(installed ? "ic_message_delete" : "DownloadIcon")} />}
label={formatStringSplit(installed ? "UNINSTALL_TITLE" : "INSTALL_TITLE", { title: postType })}
label={formatString(installed ? "UNINSTALL_TITLE" : "INSTALL_TITLE", { title: postType })}
disabled={loading}
onPress={installOrRemove}
/>

View file

@ -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: <RN.Text style={TextStyleSheet["text-md/semibold"]}>{urlType}</RN.Text> }),
content: formatString("CONFIRMATION_LINK_IS_A_TYPE", { urlType }),
onConfirm: () => installWithToast(urlType, url),
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,