diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 92fd6732..dd7dced5 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -17,7 +17,9 @@ "copied": "Kopiert!", "confirm": "Bestätigen", "choose-folder": "Ordner Wählen", - "unknown": "Ein unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯" + "unknown": "Ein unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯", + "warning": "Achtung", + "continue": "Weiter" }, "nav-bar": { "add-version": "Version hinzufügen", @@ -838,6 +840,9 @@ "proton-folder-description": "Unter Linux benötigt BSManager Proton, um zu funktionieren. Wählen Sie den Installationsordner von Proton, um fortzufahren.", "proton-folder-placeholder": "Proton-Installationsordner", "where-is-proton-installed": "Wo ist Proton installiert?" + }, + "bs-version-outdated": { + "body": "Die Version {outdatedVersion} ist veraltet, und einige Mods oder Funktionen funktionieren möglicherweise nicht mehr wie erwartet. Bitte lade die neueste empfohlene Version ({recommendedVersion}) von Beat Saber herunter, um die neuesten Funktionen und Fehlerbehebungen zu nutzen." } }, "maps": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index a0517d94..9f54797e 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -17,7 +17,9 @@ "copied": "Copied!", "confirm": "Confirm", "choose-folder": "Choose folder", - "unknown": "An unknown error occurred ¯\\_(ツ)_/¯" + "unknown": "An unknown error occurred ¯\\_(ツ)_/¯", + "warning": "Warning", + "continue": "Continue" }, "nav-bar": { "add-version": "Add a version", @@ -838,6 +840,9 @@ "proton-folder-description": "On Linux, BSManager requires Proton to work. Choose the Proton installation folder to continue.", "proton-folder-placeholder": "Proton installation folder", "where-is-proton-installed": "Where is Proton installed?" + }, + "bs-version-outdated": { + "body": "The version {outdatedVersion} is outdated, and some mods or features may no longer work as expected. Please download the latest recommended version ({recommendedVersion}) of Beat Saber to enjoy the latest features and bugfixes." } }, "maps": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index bb4c2298..764cd4ca 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -17,7 +17,9 @@ "copied": "¡Copiado!", "confirm": "Confirmar", "choose-folder": "Elige la carpeta", - "unknown": "Se ha producido un error desconocido ¯\\_(ツ)_/¯" + "unknown": "Se ha producido un error desconocido ¯\\_(ツ)_/¯", + "warning": "Advertencia", + "continue": "Continuar" }, "nav-bar": { "add-version": "Agregar una versión", @@ -838,6 +840,9 @@ "proton-folder-description": "En Linux, BSManager necesita Proton para funcionar. Elija la carpeta de instalación de Proton para continuar.", "proton-folder-placeholder": "Carpeta de instalación de Proton", "where-is-proton-installed": "¿Dónde está instalado Proton?" + }, + "bs-version-outdated": { + "body": "La versión {outdatedVersion} está obsoleta, y algunos mods o funciones pueden no funcionar como se espera. Por favor, descarga la última versión recomendada ({recommendedVersion}) de Beat Saber para disfrutar de las últimas funciones y correcciones de errores." } }, "maps": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index ef4ff496..e484cfba 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -17,7 +17,9 @@ "copied": "Copié !", "confirm": "Confirmer", "choose-folder": "Choisir un dossier", - "unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯" + "unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯", + "warning": "Attention", + "continue": "Continuer" }, "nav-bar": { "add-version": "Ajouter une version", @@ -839,6 +841,9 @@ "proton-folder-description": "Sous Linux, BSManager a besoin de Proton pour fonctionner. Choisissez le dossier d'installation de Proton pour continuer.", "proton-folder-placeholder": "Dossier d'installation de Proton", "where-is-proton-installed": "Où est installé Proton ?" + }, + "bs-version-outdated": { + "body": "La version {outdatedVersion} est obsolète, et certains mods ou fonctionnalités peuvent ne plus fonctionner comme prévu. Veuillez télécharger la dernière version recommandée ({recommendedVersion}) de Beat Saber pour profiter des dernières fonctionnalités et corrections de bugs." } }, "maps": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 76f8011f..49faa14c 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -17,7 +17,9 @@ "copied": "コピー済み!", "confirm": "確認", "choose-folder": "フォルダーを選択", - "unknown": "不明なエラーが発生しました (´・ω・`)" + "unknown": "不明なエラーが発生しました (´・ω・`)", + "warning": "警告", + "continue": "続ける" }, "nav-bar": { "add-version": "バージョンを追加", @@ -838,6 +840,9 @@ "proton-folder-description": "Linuxでは、BSManagerを動作させるためにProtonが必要です。続行するには、Protonのインストールフォルダを選択してください。", "proton-folder-placeholder": "Protonのインストールフォルダ", "where-is-proton-installed": "Protonはどこにインストールされていますか?" + }, + "bs-version-outdated": { + "body": "バージョン {outdatedVersion} は古いため、一部のモッドや機能が期待通りに動作しない場合があります。最新の機能やバグ修正を楽しむために、推奨される最新バージョンの Beat Saber ({recommendedVersion}) をダウンロードしてください。" } }, "maps": { diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index a2381fcd..3ba2a1d8 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -17,7 +17,9 @@ "copied": "복사 완료!", "confirm": "확인", "choose-folder": "폴더를 선택하세요", - "unknown": "알 수 없는 오류가 발생했습니다 (´・ω・`)" + "unknown": "알 수 없는 오류가 발생했습니다 (´・ω・`)", + "warning": "경고", + "continue": "계속" }, "nav-bar": { "add-version": "버전 추가", @@ -838,6 +840,9 @@ "proton-folder-description": "Linux에서는 BSManager가 작동하려면 Proton이 필요합니다. 계속하려면 Proton 설치 폴더를 선택하세요.", "proton-folder-placeholder": "Proton 설치 폴더", "where-is-proton-installed": "Proton이 설치된 위치는 어디인가요?" + }, + "bs-version-outdated": { + "body": "버전 {outdatedVersion}은(는) 오래되어 일부 모드나 기능이 예상대로 작동하지 않을 수 있습니다. 최신 기능과 버그 수정 사항을 즐기기 위해 Beat Saber의 최신 권장 버전 ({recommendedVersion})을 다운로드하십시오." } }, "maps": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index f2044880..e1536105 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -17,7 +17,9 @@ "copied": "Скопировано!", "confirm": "Подтвердить", "choose-folder": "Изменить папку", - "unknown": "Неизвестная ошибка ¯\\_(ツ)_/¯" + "unknown": "Неизвестная ошибка ¯\\_(ツ)_/¯", + "warning": "Внимание", + "continue": "Продолжить" }, "nav-bar": { "add-version": "Добавить версию игры", @@ -837,6 +839,9 @@ "proton-folder-description": "В Linux BSManager требует Proton для работы. Выберите папку установки Proton, чтобы продолжить.", "proton-folder-placeholder": "Папка установки Proton", "where-is-proton-installed": "Где установлен Proton?" + }, + "bs-version-outdated": { + "body": "Версия {outdatedVersion} устарела, и некоторые моды или функции могут работать некорректно. Пожалуйста, скачайте последнюю рекомендуемую версию ({recommendedVersion}) Beat Saber, чтобы воспользоваться новейшими функциями и исправлениями ошибок." } }, "maps": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 55fc0e51..39a81e93 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -17,7 +17,9 @@ "copied": "已複製!", "confirm": "確認", "choose-folder": "選擇文件夾", - "unknown": "發生了一個錯誤 ¯\\_(ツ)_/¯" + "unknown": "發生了一個錯誤 ¯\\_(ツ)_/¯", + "warning": "警告", + "continue": "繼續" }, "nav-bar": { "add-version": "新增版本", @@ -838,6 +840,9 @@ "proton-folder-description": "在Linux上,BSManager需要Proton才能運行。請選擇Proton的安裝資料夾以繼續。", "proton-folder-placeholder": "Proton安裝資料夾", "where-is-proton-installed": "Proton安裝在哪裡?" + }, + "bs-version-outdated": { + "body": "版本 {outdatedVersion} 已過時,某些模組或功能可能無法正常運作。請下載最新推薦的 Beat Saber 版本 ({recommendedVersion}),以享受最新功能和修正。" } }, "maps": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 9489c6a6..8a144419 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -17,7 +17,9 @@ "copied": "已复制!", "confirm": "确认", "choose-folder": "选择文件夹", - "Unknown": "发生了一个错误 ¯\\_(ツ)_/¯" + "Unknown": "发生了一个错误 ¯\\_(ツ)_/¯", + "warning": "警告", + "continue": "继续" }, "nav-bar": { "add-version": "添加版本", @@ -838,6 +840,9 @@ "proton-folder-description": "在Linux上,BSManager需要Proton才能运行。请选择Proton的安装文件夹以继续。", "proton-folder-placeholder": "Proton安装文件夹", "where-is-proton-installed": "Proton安装在哪里?" + }, + "bs-version-outdated": { + "body": "版本 {outdatedVersion} 已过时,某些模组或功能可能无法正常工作。请下载最新推荐的 Beat Saber 版本 ({recommendedVersion}),以享受最新功能和修复。" } }, "maps": { diff --git a/src/renderer/components/modal/modal-types/bs-version-outdated-modal.component.tsx b/src/renderer/components/modal/modal-types/bs-version-outdated-modal.component.tsx new file mode 100644 index 00000000..869130e0 --- /dev/null +++ b/src/renderer/components/modal/modal-types/bs-version-outdated-modal.component.tsx @@ -0,0 +1,42 @@ +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { BsmImage } from "renderer/components/shared/bsm-image.component"; +import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"; +import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png"; +import { BSVersion } from "shared/bs-version.interface"; +import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component"; +import { useTranslationV2 } from "renderer/hooks/use-translation.hook"; +import { useState } from "react"; +import { ConfigurationService } from "renderer/services/configuration.service"; +import { useService } from "renderer/hooks/use-service.hook"; + +export const BSVersionOutdatedModal: ModalComponent = ({ resolver, options: {data : { outdated, recommended }}}) => { + + const t = useTranslationV2(); + const config = useService(ConfigurationService); + + const [remember, setRemember] = useState(false); + + const handleContinue = () => { + if (remember) { + config.set("not-show-bs-version-outdated", true); + } + + resolver({ exitCode: ModalExitCode.COMPLETED }); + }; + + return ( +
+

{t.text("misc.warning")}

+ +

{t.element("modals.bs-version-outdated.body", { outdatedVersion: {outdated.BSVersion}, recommendedVersion: {recommended.BSVersion} })}

+
+ setRemember(() => val)} /> + {t.text("modals.misc.remember-my-choice")} +
+
+ resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" /> + +
+ + ); +}; diff --git a/src/renderer/hooks/use-translation.hook.ts b/src/renderer/hooks/use-translation.hook.ts index 542e2fd8..83b1769a 100644 --- a/src/renderer/hooks/use-translation.hook.ts +++ b/src/renderer/hooks/use-translation.hook.ts @@ -1,7 +1,12 @@ import { I18nService } from "renderer/services/i18n.service"; import { useService } from "./use-service.hook"; import { useObservable } from "./use-observable.hook"; +import { useConstant } from "./use-constant.hook"; +import { createElement, Fragment } from "react"; +/** + * @deprecated Use useTranslationV2 instead + */ export function useTranslation(): (translationKey: string, args?: Record) => string { const i18nService = useService(I18nService); @@ -15,3 +20,35 @@ export function useTranslation(): (translationKey: string, args?: Record) => string; + element: (translationKey: string, args?: Record) => JSX.Element; +} + +export function useTranslationV2(): TranslateFunctions { + + const i18nService = useService(I18nService); + + useObservable(() => i18nService.currentLanguage$); + + const text = useConstant(() => { + return (key: string, args?: Record) => { + if (!key) { + return key; + } + return i18nService.translate(key, args); + }; + }); + + const element = useConstant(() => { + return (key: string, args?: Record) => { + if (!key) { + return createElement(Fragment, null, key); + } + return i18nService.translateElement(key, args); + }; + }); + + return { text, element }; +} diff --git a/src/renderer/pages/available-versions-list.components.tsx b/src/renderer/pages/available-versions-list.components.tsx index 8be40307..b781282d 100644 --- a/src/renderer/pages/available-versions-list.components.tsx +++ b/src/renderer/pages/available-versions-list.components.tsx @@ -12,6 +12,11 @@ import { BSVersion } from "shared/bs-version.interface"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service"; import { logRenderError } from "renderer"; +import { ModalExitCode, ModalService } from "renderer/services/modale.service"; +import { BSVersionOutdatedModal } from "renderer/components/modal/modal-types/bs-version-outdated-modal.component"; +import { ConfigurationService } from "renderer/services/configuration.service"; +import { coerce, lt, valid } from "semver"; +import { tryit } from "shared/helpers/error.helpers"; export const AvailableVersionsContext = createContext<{ selectedVersion: BSVersion; setSelectedVersion: (version: BSVersion) => void }>(null); @@ -19,6 +24,8 @@ export function AvailableVersionsList() { const bsDownloader = useService(BsDownloaderService); const versionManager = useService(BSVersionManagerService); + const modals = useService(ModalService); + const config = useService(ConfigurationService); const [selectedVersion, setSelectedVersion] = useState(null); const contextValue = useMemo(() => ({ selectedVersion, setSelectedVersion }), [selectedVersion]); @@ -26,9 +33,24 @@ export function AvailableVersionsList() { const downloading = useObservable(() => bsDownloader.downloadingVersion$.pipe(map(v => !!v))); const t = useTranslation(); - const startDownload = async () => { + const startDownload = async (version: BSVersion) => { + const showOutdated = !config.get("not-show-bs-version-outdated"); + const recommendedVersion = showOutdated ? versionManager.availableVersions$.value.find(v => v.recommended) : undefined; - return bsDownloader.downloadVersion(selectedVersion) + const isVersionOutdated = recommendedVersion?.BSVersion ? ( + tryit(() => lt(valid(coerce(version.BSVersion)), valid(coerce(recommendedVersion.BSVersion)))).result ?? false + ) : false; + + if (showOutdated && isVersionOutdated) { + + const res = await modals.openModal(BSVersionOutdatedModal, {data: { outdated: version, recommended: recommendedVersion }}); + + if (res.exitCode !== ModalExitCode.COMPLETED) { + return; + } + } + + return bsDownloader.downloadVersion(version) .catch(logRenderError) .finally(() => setSelectedVersion(null)); }; @@ -56,7 +78,7 @@ export function AvailableVersionsList() { {selectedVersion && !downloading && ( - + startDownload(selectedVersion)}> )} diff --git a/src/renderer/services/i18n.service.ts b/src/renderer/services/i18n.service.ts index 08a498b2..859d0f19 100644 --- a/src/renderer/services/i18n.service.ts +++ b/src/renderer/services/i18n.service.ts @@ -4,6 +4,7 @@ import { Observable } from "rxjs"; import { ConfigurationService } from "./configuration.service"; import { getProperty } from "dot-prop"; import { i18n } from "dateformat"; +import { createElement, Fragment } from "react"; export class I18nService { private static instance: I18nService; @@ -90,4 +91,30 @@ export class I18nService { return translated; } + + public translateElement(translationKey: string, args?: Record): JSX.Element { + let translated = this.cache.get(translationKey); + if (!translated) { + translated = getProperty(this.dictionary, translationKey) ?? translationKey; + this.cache.set(translationKey, translated); + } + + if (!args) { + return createElement(Fragment, null, translated); + } + + // Sort keys by length in descending order to avoid partial matches within keys + const sortedKeys = Object.keys(args).sort((a, b) => b.length - a.length); + + const pattern = sortedKeys.map(key => `\\{${key}\\}`).join('|'); + const regex = new RegExp(`(${pattern})`, 'g'); + const segments = translated.split(regex); + + const children = segments.map((segment) => { + const key = segment.slice(1, -1); // Remove surrounding {} + return args[key] || segment; + }); + + return createElement(Fragment, null, ...children); + } }