1
0
mirror of https://github.com/upscayl/upscayl.git synced 2024-11-27 17:00:52 +01:00

ADD: en, ru, ja strings; MOD: Actual strings with t(*) variables

This commit is contained in:
abhishek-gaonkar 2024-09-01 17:31:45 +05:30 committed by Aaron Liu
parent 326aaca7e4
commit bacbee8fa8
No known key found for this signature in database
GPG Key ID: 2D4DA57B12065A35
34 changed files with 1255 additions and 230 deletions

8
package-lock.json generated
View File

@ -8168,11 +8168,11 @@
]
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {

View File

@ -1,11 +1,12 @@
import { atom } from "jotai";
import en from "../locales/en.json";
import ru from "../locales/ru.json";
import ja from "../locales/ja.json";
import { atomWithStorage } from "jotai/utils";
// Define the shape of the translations
type Translations = typeof en;
type Locales = "en" | "ru";
type Locales = "en" | "ru" | "ja";
// Utility function to access nested translation keys
const getNestedTranslation = (obj: Translations, key: string): string => {
@ -20,7 +21,7 @@ export const localeAtom = atomWithStorage<Locales>("language", "en");
// Atom to get the translation function based on the current locale
export const translationAtom = atom((get) => {
const locale = get(localeAtom);
const translations: Record<Locales, Translations> = { en, ru };
const translations: Record<Locales, Translations> = { en, ru, ja };
return (key: string, params: Record<string, string> = {}): string => {
const template = getNestedTranslation(translations[locale], key);

View File

@ -1,36 +1,41 @@
import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom";
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue, useSetAtom } from "jotai";
import React from "react";
function Footer() {
const setShowNewsModal = useSetAtom(showNewsModalAtom);
const news = useAtomValue(newsAtom);
const t = useAtomValue(translationAtom);
return (
<div className="p-2 text-center text-xs text-base-content/50">
{news && !news?.data?.dontShow && (
<button
className="badge badge-neutral mb-2"
onClick={() => setShowNewsModal(true)}>
UPSCAYL NEWS
onClick={() => setShowNewsModal(true)}
>
{t("APP.FOOTER.NEWS_TITLE")}
</button>
)}
<p>
Copyright © {new Date().getFullYear()} -{" "}
{t("APP.FOOTER.COPYRIGHT")} {new Date().getFullYear()} -{" "}
<a
className="font-bold"
href="https://github.com/upscayl/upscayl"
target="_blank">
Upscayl
target="_blank"
>
{t("APP.TITLE")}
</a>
</p>
<p>
By{" "}
{t("APP.FOOTER.BY")}
<a
href="https://github.com/upscayl"
className="font-bold"
target="_blank">
The Upscayl Team
target="_blank"
>
{t("APP.FOOTER.APP_TEAM")}
</a>
</p>
</div>

View File

@ -1,26 +1,30 @@
import { featureFlags } from "@common/feature-flags";
import React from "react";
import Logo from "./icons/Logo";
import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom";
export default function Header({ version }: { version: string }) {
const t = useAtomValue(translationAtom);
return (
<a
href="https://github.com/upscayl/upscayl"
target="_blank"
className={`outline-none focus-visible:ring-2`}
data-tooltip-id="tooltip"
data-tooltip-content="Star us on GitHub 😁"
data-tooltip-content={t("APP.HEADER.GITHUB_STAR_TT_INFO")}
>
<div className="flex items-center gap-3 px-5 py-5">
<Logo className="inline-block h-14 w-14" />
<div className="flex flex-col justify-center">
<h1 className="text-3xl font-bold">
Upscayl{" "}
{t("APP.TITLE")}
<span className="text-xs">
{version} {featureFlags.APP_STORE_BUILD && "Mac"}
</span>
</h1>
<p className="">AI Image Upscaler</p>
<p className="">{t("APP.HEADER.APP_INFO")}</p>
</div>
</div>
</a>

View File

@ -1,4 +1,6 @@
import { translationAtom } from "@/atoms/translations-atom";
import { GrayMatterFile } from "gray-matter";
import { useAtomValue } from "jotai";
import React from "react";
import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
@ -12,17 +14,21 @@ export const NewsModal = ({
setShow: React.Dispatch<React.SetStateAction<boolean>>;
news: GrayMatterFile<string>;
}) => {
const t = useAtomValue(translationAtom);
return (
<dialog className={`modal ${show && "modal-open"}`}>
<div className="modal-box flex flex-col text-center items-center gap-4">
<div className="modal-box flex flex-col items-center gap-4 text-center">
<button
className="absolute top-2 right-4 btn btn-circle"
onClick={() => setShow(false)}>
className="btn btn-circle absolute right-4 top-2"
onClick={() => setShow(false)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24">
viewBox="0 0 24 24"
>
<rect
x="0"
y="0"
@ -51,7 +57,9 @@ export const NewsModal = ({
</div>
<form method="dialog" className="modal-backdrop">
<button onClick={() => setShow(false)}>close</button>
<button onClick={() => setShow(false)}>
{t("APP.INFOS.DIALOG_BOX.CLOSE")}
</button>
</form>
</dialog>
);

View File

@ -1,3 +1,5 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
type TabsProps = {
@ -6,6 +8,8 @@ type TabsProps = {
};
const Tabs = ({ selectedTab, setSelectedTab }: TabsProps) => {
const t = useAtomValue(translationAtom);
return (
<div className="tabs-boxed tabs mx-auto mb-2">
<a
@ -14,7 +18,7 @@ const Tabs = ({ selectedTab, setSelectedTab }: TabsProps) => {
setSelectedTab(0);
}}
>
Upscayl
{t("APP.TITLE")}
</a>
<a
className={`tab ${selectedTab === 1 && "tab-active"}`}
@ -22,7 +26,7 @@ const Tabs = ({ selectedTab, setSelectedTab }: TabsProps) => {
setSelectedTab(1);
}}
>
Settings
{t("APP.SETTINGS.TITLE")}
</a>
</div>
);

View File

@ -1,6 +1,8 @@
import { useState } from "react";
import { waitlistCollection } from "../firebase";
import { doc, setDoc } from "firebase/firestore";
import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom";
const nameRegex = /^[A-Za-z\s.'-]+$/;
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
@ -8,18 +10,21 @@ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
export const UpscaylCloudModal = ({ show, setShow, setDontShowCloudModal }) => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const t = useAtomValue(translationAtom);
return (
<dialog className={`modal ${show && "modal-open"}`}>
<div className="modal-box flex flex-col text-center items-center gap-4">
<div className="modal-box flex flex-col items-center gap-4 text-center">
<button
className="absolute top-2 right-4 btn btn-circle"
onClick={() => setShow(false)}>
className="btn btn-circle absolute right-4 top-2"
onClick={() => setShow(false)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24">
viewBox="0 0 24 24"
>
<rect
x="0"
y="0"
@ -37,22 +42,18 @@ export const UpscaylCloudModal = ({ show, setShow, setDontShowCloudModal }) => {
/>
</svg>
</button>
<p className="badge badge-neutral text-xs">Coming soon!</p>
<p className="text-2xl font-semibold">Introducing Upscayl Cloud!</p>
<p className="w-9/12 font-medium text-lg">
No more errors, hardware issues, quality compromises or long loading
times!
<p className="badge badge-neutral text-xs">
{t("APP.UPSCAYL_CLOUD.COMING_SOON")}
</p>
<p className="text-2xl font-semibold">{t("APP.INTRO")}</p>
<p className="w-9/12 text-lg font-medium">
{t("APP.UPSCAYL_CLOUD.CATCHY_PHRASE_1")}
</p>
<div className="flex flex-col gap-2 text-start">
<p>🌐 Upscayl anywhere, anytime, any device</p>
<p> No Graphics Card or hardware required</p>
<p>👩 Face Enhancement</p>
<p>🦋 10+ models to choose from</p>
<p>🏎 5x faster than Upscayl Desktop</p>
<p>🎞 Video Upscaling</p>
<p>💰 Commercial Usage</p>
<p>😴 Upscayl while you sleep</p>
<pre style={{ fontFamily: "inherit" }} className="leading-8">
{t("APP.UPSCAYL_CLOUD.CATCHY_PHRASE_2")}
</pre>
</div>
<form
@ -71,23 +72,20 @@ export const UpscaylCloudModal = ({ show, setShow, setDontShowCloudModal }) => {
email,
});
} catch (error) {
alert(
`Thank you ${name}! It seems that your email has already been registered :D If that's not the case, please try again.`
);
alert(t("APP.UPSCAYL_CLOUD.ALREADY_REGISTERED", { name }));
return;
}
setName("");
setEmail("");
setDontShowCloudModal(true);
setShow(false);
alert(
"Thank you for joining the waitlist! We will notify you when Upscayl Cloud is ready for you."
);
alert(t("APP.UPSCAYL_CLOUD.ADD_SUCCESS"));
} else {
alert("Please fill in all the fields correctly.");
alert(t("APP.UPSCAYL_CLOUD.INCORRECT_FIELDS"));
}
}}>
<div className="gap-2 grid grid-cols-2">
}}
>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
className="input input-bordered"
@ -105,8 +103,9 @@ export const UpscaylCloudModal = ({ show, setShow, setDontShowCloudModal }) => {
</div>
<button
type="submit"
className="bg-success text-success-content rounded-2xl px-4 py-2">
Join the waitlist
className="rounded-2xl bg-success px-4 py-2 text-success-content"
>
{t("APP.UPSCAYL_CLOUD.JOIN_WAITLIST")}
</button>
<button
@ -115,14 +114,17 @@ export const UpscaylCloudModal = ({ show, setShow, setDontShowCloudModal }) => {
setDontShowCloudModal(true);
setShow(false);
}}
type="button">
DON'T SHOW AGAIN
type="button"
>
{t("APP.UPSCAYL_CLOUD.DONT_SHOW_AGAIN")}
</button>
</form>
</div>
<form method="dialog" className="modal-backdrop">
<button onClick={() => setShow(false)}>close</button>
<button onClick={() => setShow(false)}>
{t("APP.INFOS.DIALOG_BOX.CLOSE")}
</button>
</form>
</dialog>
);

View File

@ -1,3 +1,6 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
type CompressionInputProps = {
compression: number;
handleCompressionChange: (arg: any) => void;
@ -7,16 +10,20 @@ export function CompressionInput({
compression,
handleCompressionChange,
}: CompressionInputProps) {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<div className="flex gap-1 text-sm font-medium uppercase">
<p className="shrink-0">Image Compression ({compression}%)</p>
<p className="shrink-0">
{t("APP.INFOS.IMAGE_COMPRESSION.TITLE", {
compression: compression.toString(),
})}
</p>
</div>
{compression > 0 && (
<p className="text-xs text-base-content/80">
PNG compression is lossless, so it might not reduce the file size
significantly and higher compression values might affect the
performance. JPG and WebP compression is lossy.
{t("APP.INFOS.IMAGE_COMPRESSION.LOSSLESS_TIP")}
</p>
)}
<input

View File

@ -1,5 +1,7 @@
import React from "react";
import commands from "../../../common/commands";
import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom";
type CustomModelsFolderSelectProps = {
customModelsPath: string;
@ -10,24 +12,27 @@ export function CustomModelsFolderSelect({
customModelsPath,
setCustomModelsPath,
}: CustomModelsFolderSelectProps) {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col items-start gap-2">
<p className="text-sm font-medium">ADD CUSTOM MODELS</p>
<p className="text-sm font-medium">{t("APP.INFOS.CUSTOM_MODELS.ADD")}</p>
<p className="text-xs text-base-content/80">
You can add your own models easily. For more details:{" "}
{t("APP.INFOS.CUSTOM_MODELS.INFO")}
<a
href="https://github.com/upscayl/custom-models/blob/main/README.md"
className="underline link"
target="_blank">
Custom Models Repository
className="link underline"
target="_blank"
>
{t("APP.INFOS.CUSTOM_MODELS.LINK_TEXT")}
</a>
</p>
<p className="text-sm text-base-content/60">{customModelsPath}</p>
<button
className="btn-primary btn"
className="btn btn-primary"
onClick={async () => {
const customModelPath = await window.electron.invoke(
commands.SELECT_CUSTOM_MODEL_FOLDER
commands.SELECT_CUSTOM_MODEL_FOLDER,
);
if (customModelPath !== null) {
@ -36,8 +41,9 @@ export function CustomModelsFolderSelect({
} else {
setCustomModelsPath("");
}
}}>
Select Folder
}}
>
{t("APP.INFOS.CUSTOM_MODELS.SELECT_FOLDER")}
</button>
</div>
);

View File

@ -1,21 +1,23 @@
import { customWidthAtom, useCustomWidthAtom } from "@/atoms/userSettingsAtom";
import { useAtom } from "jotai";
import React, { useState } from "react";
import { Input } from "../ui/input";
import { useAtom, useAtomValue } from "jotai";
import React from "react";
import { translationAtom } from "@/atoms/translations-atom";
export function CustomResolutionInput() {
const [useCustomWidth, setUseCustomWidth] = useAtom(useCustomWidthAtom);
const [customWidth, setCustomWidth] = useAtom(customWidthAtom);
const t = useAtomValue(translationAtom);
return (
<div>
<div className="flex flex-col gap-1">
<p className="text-sm font-medium">CUSTOM OUTPUT WIDTH</p>
<p className="text-sm font-medium">
{t("APP.INFOS.CUSTOM_INPUT_RESOLUTION.WIDTH")}
</p>
<p className="text-xs text-base-content/80">
<b>REQUIRES RESTART</b>
<b>{t("APP.INFOS.CUSTOM_INPUT_RESOLUTION.RESTART")}</b>
<br />
Use a custom width for the output images. The height will be adjusted
automatically. Enabling this will override the scale setting.
{t("APP.INFOS.CUSTOM_INPUT_RESOLUTION.CUSTOM_WIDTH_TIP")}
</p>
</div>
<div className="mt-2 flex items-center gap-2">

View File

@ -1,14 +1,19 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
export function DonateButton({}) {
export function DonateButton() {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2 text-sm font-medium">
<p>If you like what we do :)</p>
<p>{t("APP.INFOS.DONATE.IF_LIKED")}</p>
<a
href="https://buymeacoffee.com/fossisthefuture"
target="_blank"
className="btn btn-primary"
>
💎 DONATE
{t("APP.INFOS.DONATE.DONATE")}
</a>
</div>
);

View File

@ -1,3 +1,5 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
type GpuIdInputProps = {
@ -6,15 +8,17 @@ type GpuIdInputProps = {
};
export function GpuIdInput({ gpuId, handleGpuIdChange }) {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">GPU ID</p>
<p className="text-sm font-medium">{t("APP.INFOS.GPU_ID_INPUT.ID")}</p>
<p className="text-xs text-base-content/80">
Please read the Upscayl Documentation for more information.
{t("APP.INFOS.GPU_ID_INPUT.READ_DOCS")}
</p>
{window.electron.platform === "win" && (
<p className="text-xs text-base-content/80">
Enable performance mode on Windows for better results.
{t("APP.INFOS.GPU_ID_INPUT.ENABLE_PERF_MODE")}
</p>
)}
<input

View File

@ -1,3 +1,6 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
type ImageFormatSelectProps = {
batchMode: boolean;
saveImageAs: string;
@ -9,10 +12,14 @@ export function ImageFormatSelect({
saveImageAs,
setExportType,
}: ImageFormatSelectProps) {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-1">
<p className="text-sm font-medium">SAVE IMAGE AS</p>
<p className="text-sm font-medium">
{t("APP.INFOS.IMAGE_FORMAT.SAVE_AS")}
</p>
{/* <p className="badge-primary badge text-[10px] font-medium">
EXPERIMENTAL
</p> */}
@ -25,21 +32,21 @@ export function ImageFormatSelect({
className={`btn ${saveImageAs === "png" && "btn-primary"}`}
onClick={() => setExportType("png")}
>
PNG
{t("APP.INFOS.IMAGE_FORMAT.PNG")}
</button>
{/* JPG */}
<button
className={`btn ${saveImageAs === "jpg" && "btn-primary"}`}
onClick={() => setExportType("jpg")}
>
JPG
{t("APP.INFOS.IMAGE_FORMAT.JPG")}
</button>
{/* WEBP */}
<button
className={`btn ${saveImageAs === "webp" && "btn-primary"}`}
onClick={() => setExportType("webp")}
>
WEBP
{t("APP.INFOS.IMAGE_FORMAT.WEBP")}
</button>
</div>
</div>

View File

@ -1,5 +1,6 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useCustomWidthAtom } from "@/atoms/userSettingsAtom";
import { useAtom, useAtomValue } from "jotai";
import { useAtomValue } from "jotai";
type ImageScaleSelectProps = {
scale: string;
@ -13,6 +14,7 @@ export function ImageScaleSelect({
hideInfo,
}: ImageScaleSelectProps) {
const useCustomWidth = useAtomValue(useCustomWidthAtom);
const t = useAtomValue(translationAtom);
return (
<div className={`${useCustomWidth && "opacity-50"}`}>
@ -20,13 +22,20 @@ export function ImageScaleSelect({
{hideInfo ? (
<>
<p className="text-sm">
Image Scale <span className="text-xs">({scale}X)</span>
{t("APP.INFOS.IMAGE_SCALE.TITLE")}{" "}
<span className="text-xs">
{t("APP.INFOS.IMAGE_SCALE.SCALES_TIMES", {
scale,
})}
</span>
</p>
{hideInfo && parseInt(scale) >= 6 && (
<p
className="badge badge-warning text-xs font-bold"
data-tooltip-id="tooltip"
data-tooltip-content="Anything above 5X may cause performance issues on some devices!"
data-tooltip-content={t(
"APP.ERRORS.IMAGE_SCALE_WARN.PERF_ISSUE",
)}
>
!
</p>
@ -34,19 +43,22 @@ export function ImageScaleSelect({
</>
) : (
<p className="text-sm font-medium">
IMAGE SCALE ({scale}X) {useCustomWidth && "DISABLED"}
{t("APP.INFOS.IMAGE_SCALE.TITLE_CAPS")}{" "}
{t("APP.INFOS.IMAGE_SCALE.SCALES_TIMES", {
scale,
})}{" "}
{useCustomWidth && "DISABLED"}
</p>
)}
</div>
{!hideInfo && (
<p className="text-xs text-base-content/80">
Anything above 4X (except 16X Double Upscayl) only resizes the image
and does not use AI upscaling.
{t("APP.INFOS.IMAGE_SCALE.AI_UPSCALE_RESIZE_INFO")}
</p>
)}
{!hideInfo && parseInt(scale) >= 6 && (
<p className="text-xs text-base-content/80 text-red-500">
This may cause performance issues on some devices!
{t("APP.ERRORS.IMAGE_SCALE_WARN.PERF_ISSUE_DEVICE")}
</p>
)}

View File

@ -1,3 +1,5 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React, { useEffect } from "react";
type LogAreaProps = {
@ -12,6 +14,7 @@ export function LogArea({
logData,
}: LogAreaProps) {
const ref = React.useRef<HTMLElement>(null);
const t = useAtomValue(translationAtom);
useEffect(() => {
if (ref.current) {
@ -24,15 +27,21 @@ export function LogArea({
<div className="flex items-center gap-2">
<p className="text-sm font-medium">LOGS</p>
<button className="btn btn-primary btn-xs" onClick={copyOnClickHandler}>
{isCopied ? <span>COPIED </span> : <span>COPY LOGS 📋</span>}
{isCopied ? (
<span>{t("APP.INFOS.LOG_AREA.ON_COPY")}</span>
) : (
<span>{t("APP.INFOS.LOG_AREA.COPY")}</span>
)}
</button>
</div>
<code
className="rounded-btn relative flex h-52 max-h-52 flex-col gap-3 overflow-y-auto break-all rounded-r-none bg-base-200 p-4 text-xs"
className="relative flex h-52 max-h-52 flex-col gap-3 overflow-y-auto break-all rounded-btn rounded-r-none bg-base-200 p-4 text-xs"
ref={ref}
>
{logData.length === 0 && (
<p className="text-base-content/70">No logs to show</p>
<p className="text-base-content/70">
{t("APP.INFOS.LOG_AREA.NO_LOGS")}
</p>
)}
{logData.map((logLine: any) => {

View File

@ -1,16 +1,19 @@
import { translationAtom } from "@/atoms/translations-atom";
import { overwriteAtom } from "@/atoms/userSettingsAtom";
import { useAtom } from "jotai";
import { useAtom, useAtomValue } from "jotai";
import React, { useEffect } from "react";
const OverwriteToggle = () => {
const [overwrite, setOverwrite] = useAtom(overwriteAtom);
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">OVERWRITE PREVIOUS UPSCALE</p>
<p className="text-sm font-medium">
{t("APP.INFOS.OVERWRITE_TOGGLE.OW_PREV")}
</p>
<p className="text-xs text-base-content/80">
If enabled, Upscayl will process the image again instead of loading it
directly.
{t("APP.INFOS.OVERWRITE_TOGGLE.OW_TIP")}
</p>
<input
type="checkbox"

View File

@ -1,17 +1,22 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
export function ResetSettings() {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col items-start gap-2">
<p className="text-sm font-medium">RESET UPSCAYL</p>
<p className="text-sm font-medium">
{t("APP.INFOS.RESET_SETTINGS.TITLE")}
</p>
<button
className="btn btn-primary"
onClick={async () => {
localStorage.clear();
alert("Upscayl has been reset. Please restart the app.");
alert(t("APP.INFOS.RESET_SETTINGS.ON_RESET"));
}}
>
RESET UPSCAYL
{t("APP.INFOS.RESET_SETTINGS.TITLE")}
</button>
</div>
);

View File

@ -1,19 +1,24 @@
import { translationAtom } from "@/atoms/translations-atom";
import {
savedOutputPathAtom,
rememberOutputFolderAtom,
} from "@/atoms/userSettingsAtom";
import { useAtom } from "jotai";
import { useAtom, useAtomValue } from "jotai";
export function SaveOutputFolderToggle() {
const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom);
const [rememberOutputFolder, setRememberOutputFolder] = useAtom(
rememberOutputFolderAtom,
);
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">SAVE OUTPUT FOLDER</p>
<p className="text-sm font-medium">
{t("APP.INFOS.SAVE_OUTPUT_FOLDER.TITLE")}
</p>
<p className="text-xs text-base-content/80">
If enabled, the output folder will be remembered between sessions.
{t("APP.INFOS.SAVE_OUTPUT_FOLDER.DESC")}
</p>
<p className="font-mono text-sm">{outputPath}</p>

View File

@ -1,3 +1,5 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
export function ThemeSelect() {
const availableThemes = [
@ -32,11 +34,12 @@ export function ThemeSelect() {
{ label: "coffee", value: "coffee" },
{ label: "winter", value: "winter" },
];
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">UPSCAYL THEME</p>
<select data-choose-theme className="select-primary select">
<p className="text-sm font-medium">{t("APP.INFOS.THEME.TITLE")}</p>
<select data-choose-theme className="select select-primary">
{availableThemes.map((theme) => {
return (
<option value={theme.value} key={theme.value}>

View File

@ -1,18 +1,21 @@
import { translationAtom } from "@/atoms/translations-atom";
import { tileSizeAtom } from "@/atoms/userSettingsAtom";
import { useAtom } from "jotai";
import { useAtom, useAtomValue } from "jotai";
import React from "react";
export function TileSizeInput() {
const [tileSize, setTileSize] = useAtom(tileSizeAtom);
const t = useAtomValue(translationAtom);
return (
<div>
<div className="flex flex-col gap-1">
<p className="text-sm font-medium">CUSTOM TILE SIZE</p>
<p className="text-sm font-medium">
{t("APP.INFOS.CUSTOM_TILE_SIZE.TITLE")}
</p>
<p className="text-xs text-base-content/80">
<br />
Use a custom tile size for segmenting the image. This can help process
images faster by reducing the number of tiles generated.
{t("APP.INFOS.CUSTOM_TILE_SIZE.DESC")}
</p>
</div>
<div className="mt-2 flex items-center gap-2">

View File

@ -1,17 +1,20 @@
import { translationAtom } from "@/atoms/translations-atom";
import { turnOffNotificationsAtom } from "@/atoms/userSettingsAtom";
import { useAtom } from "jotai";
import { useAtom, useAtomValue } from "jotai";
const TurnOffNotificationsToggle = () => {
const [turnOffNotifications, setTurnOffNotifications] = useAtom(
turnOffNotificationsAtom
turnOffNotificationsAtom,
);
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">TURN OFF NOTIFICATIONS</p>
<p className="text-sm font-medium">
{t("APP.INFOS.TURN_OFF_NOTIFICATIONS.TITLE")}
</p>
<p className="text-xs text-base-content/80">
If enabled, Upscayl will not send any system notifications on success or
failure.
{t("APP.INFOS.TURN_OFF_NOTIFICATIONS.DESC")}
</p>
<input
type="checkbox"

View File

@ -22,6 +22,7 @@ import { cn } from "@/lib/utils";
import { CustomResolutionInput } from "./CustomResolutionInput";
import { TileSizeInput } from "./TileSizeInput";
import LanguageSwitcher from "./language-switcher";
import { translationAtom } from "@/atoms/translations-atom";
interface IProps {
batchMode: boolean;
@ -69,6 +70,7 @@ function SettingsTab({
const [scale, setScale] = useAtom(scaleAtom);
const [enableScrollbar, setEnableScrollbar] = useState(true);
const [timeoutId, setTimeoutId] = useState(null);
const t = useAtomValue(translationAtom);
const { logit } = useLog();
@ -184,13 +186,13 @@ function SettingsTab({
}}
>
<div className="flex flex-col gap-2 text-sm font-medium uppercase">
<p>Having issues?</p>
<p>{t("APP.ERRORS.ISSUE_CHECK.TITLE")}</p>
<a
className="btn btn-primary"
href="https://docs.upscayl.org/"
target="_blank"
>
🙏 GET HELP
{t("APP.ERRORS.ISSUE_CHECK.GET_HELP")}
</a>
{featureFlags.APP_STORE_BUILD && (
<a
@ -198,7 +200,7 @@ function SettingsTab({
href={`mailto:upscayl@gmail.com?subject=Upscayl%20Issue%3A%20%3CIssue%20name%20here%3E&body=Device%20Name%3A%20%3CYOUR%20DEVICE%20MODEL%3E%0AOperating%20System%3A%20%3CYOUR%20OPERATING%20SYSTEM%20VERSION%3E%0AUpscayl%20Version%3A%20${upscaylVersion}%0A%0AHi%2C%20I'm%20having%20an%20issue%20with%20Upscayl.%20%3CDESCRIBE%20ISSUE%20HERE%3E`}
target="_blank"
>
📧 EMAIL DEVELOPER
{t("APP.ERRORS.ISSUE_CHECK.EMAIL_DEV")}
</a>
)}
{!featureFlags.APP_STORE_BUILD && <DonateButton />}
@ -259,7 +261,7 @@ function SettingsTab({
setShow(true);
}}
>
Introducing Upscayl Cloud
{t("APP.INTRO")}
</button>
<UpscaylCloudModal

View File

@ -4,6 +4,7 @@ import { useAtomValue, useSetAtom } from "jotai";
const locales = {
en: "English",
ru: "Русский",
ja: "日本語",
};
const LanguageSwitcher = () => {
@ -13,11 +14,11 @@ const LanguageSwitcher = () => {
return (
<div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">{t("settings.language")}</p>
<p className="text-sm font-medium">{t("APP.SETTINGS.CHANGE_LANG")}</p>
<select
data-choose-theme
className="select select-primary"
onChange={(e) => setLocale(e.target.value as any)}
onChange={(e) => setLocale(e.target.value as keyof typeof locales)}
>
{Object.entries(locales).map((entry) => {
const [locale, label] = entry;

View File

@ -1,16 +1,19 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom";
import { cn } from "@/lib/utils"
const t = useAtomValue(translationAtom);
const Dialog = DialogPrimitive.Root
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
@ -20,12 +23,12 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
className,
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
@ -37,19 +40,19 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
<span className="sr-only">{t("APP.INFOS.DIALOG_BOX.CLOSE")}</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
@ -58,12 +61,12 @@ const DialogHeader = ({
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
className,
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
@ -72,12 +75,12 @@ const DialogFooter = ({
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
className,
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
@ -87,12 +90,12 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
className,
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
@ -103,8 +106,8 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
@ -117,4 +120,4 @@ export {
DialogFooter,
DialogTitle,
DialogDescription,
}
};

View File

@ -20,6 +20,7 @@ import Select from "react-select";
import { cn } from "@/lib/utils";
import { useToast } from "@/components/ui/use-toast";
import { ImageScaleSelect } from "@/components/settings-tab/ImageScaleSelect";
import { translationAtom } from "@/atoms/translations-atom";
interface IProps {
selectImageHandler: () => Promise<void>;
@ -77,6 +78,7 @@ function LeftPaneImageSteps({
const { logit } = useLog();
const { toast } = useToast();
const t = useAtomValue(translationAtom);
const outputHandler = async () => {
var path = await window.electron.invoke(COMMAND.SELECT_FOLDER);
@ -196,30 +198,38 @@ function LeftPaneImageSteps({
<p
className="mr-1 inline-block cursor-help text-sm"
data-tooltip-id="tooltip"
data-tooltip-content="This will let you Upscayl all files in a folder at once"
data-tooltip-content={t("APP.INFOS.LEFT_PANE_PROCESS.BATCH.TT_INFO")}
>
Batch Upscayl
{t("APP.INFOS.LEFT_PANE_PROCESS.BATCH.TITLE")}
</p>
</div>
{/* STEP 1 */}
<div className="animate-step-in">
<p className="step-heading">Step 1</p>
<p className="step-heading">
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_1.TITLE")}
</p>
<button
className="btn btn-primary"
onClick={!batchMode ? selectImageHandler : selectFolderHandler}
data-tooltip-id="tooltip"
data-tooltip-content={imagePath}
>
Select {batchMode ? "Folder" : "Image"}
{batchMode
? t("APP.INFOS.LEFT_PANE_PROCESS.STEP_1.BATCH_YES")
: t("APP.INFOS.LEFT_PANE_PROCESS.STEP_1.BATCH_NO")}
</button>
</div>
{/* STEP 2 */}
<div className="animate-step-in group flex flex-col gap-4">
<div>
<p className="step-heading">Step 2</p>
<p className="mb-2 text-sm">Select Model</p>
<p className="step-heading">
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_2.TITLE")}
</p>
<p className="mb-2 text-sm">
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_2.SELECT_MODEL")}
</p>
<Select
onMenuOpen={() => setOpen(true)}
@ -262,12 +272,14 @@ function LeftPaneImageSteps({
setDoubleUpscayl(!doubleUpscayl);
}}
>
Double Upscayl
{t("APP.INFOS.LEFT_PANE_PROCESS.DOUBLE_UPSCAYL.TITLE")}
</p>
<button
className="badge badge-neutral badge-sm cursor-help"
data-tooltip-id="tooltip"
data-tooltip-content="Enable this option to run upscayl twice on an image. Note that this may cause a significant increase in processing time and possibly performance issues for scales greater than 4X."
data-tooltip-content={t(
"APP.INFOS.LEFT_PANE_PROCESS.DOUBLE_UPSCAYL.TT_INFO",
)}
>
?
</button>
@ -281,13 +293,17 @@ function LeftPaneImageSteps({
<div className="animate-step-in">
<div className="flex flex-col pb-2">
<div className="step-heading flex items-center gap-2">
<span className="leading-none">Step 3</span>
<span className="leading-none">
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_3.TITLE")}
</span>
{featureFlags.APP_STORE_BUILD && (
<button
className="badge badge-outline badge-sm cursor-pointer"
onClick={() =>
alert(
"Due to MacOS App Store security restrictions, Upscayl requires you to select an output folder everytime you start it.\n\nTo avoid this, you can permanently save a default output folder in the Upscayl 'Settings' tab.",
t(
"APP.INFOS.LEFT_PANE_PROCESS.STEP_3.MACOS_RESTRICTION_ALERT",
),
)
}
>
@ -298,14 +314,16 @@ function LeftPaneImageSteps({
{!outputPath && featureFlags.APP_STORE_BUILD && (
<div className="text-xs">
<span className="rounded-btn bg-base-200 px-2 font-medium uppercase text-base-content/50">
Not selected
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_3.NOT_SELECTED")}
</span>
</div>
)}
</div>
{!batchMode && !featureFlags.APP_STORE_BUILD && (
<p className="mb-2 text-sm">
Defaults to {!batchMode ? "Image's" : "Folder's"} path
{!batchMode
? t("APP.INFOS.LEFT_PANE_PROCESS.STEP_3.DEFAULT_IMG_PATH")
: t("APP.INFOS.LEFT_PANE_PROCESS.STEP_3.DEFAULT_FOLDER_PATH")}
</p>
)}
<button
@ -314,20 +332,22 @@ function LeftPaneImageSteps({
data-tooltip-id="tooltip"
onClick={outputHandler}
>
Set Output Folder
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_3.SET_OUTPUT_FOLDER")}
</button>
</div>
{/* STEP 4 */}
<div className="animate-step-in">
<p className="step-heading">Step 4</p>
<p className="step-heading">
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_4.TITLE")}
</p>
{dimensions.width && dimensions.height && (
<p className="mb-2 text-sm">
Upscayl from{" "}
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_4.UPSCAYL_FROM")}
<span className="font-bold">
{dimensions.width}x{dimensions.height}
</span>{" "}
to{" "}
</span>
{t("APP.INFOS.LEFT_PANE_PROCESS.STEP_4.UPSCAYL_TO")}
<span className="font-bold">
{getUpscaleResolution().width}x{getUpscaleResolution().height}
</span>
@ -339,12 +359,16 @@ function LeftPaneImageSteps({
progress.length > 0 || !outputPath
? () =>
toast({
description: "Please select an output folder first",
description: t(
"APP.INFOS.LEFT_PANE_PROCESS.STEP_4.FOLDER_ALERT",
),
})
: upscaylHandler
}
>
{progress.length > 0 ? "Upscayling⏳" : "Upscayl"}
{progress.length > 0
? t("APP.INFOS.LEFT_PANE_PROCESS.STEP_4.PROCESS_IN_PROGRESS")
: t("APP.INFOS.LEFT_PANE_PROCESS.STEP_4.PROCESS_START")}
</button>
</div>

View File

@ -1,6 +1,7 @@
import { translationAtom } from "@/atoms/translations-atom";
import { lensSizeAtom, viewTypeAtom } from "@/atoms/userSettingsAtom";
import { cn } from "@/lib/utils";
import { useAtom } from "jotai";
import { useAtom, useAtomValue } from "jotai";
import { WrenchIcon } from "lucide-react";
import { useEffect, useState } from "react";
@ -18,6 +19,7 @@ const ImageOptions = ({
const [openSidebar, setOpenSidebar] = useState(false);
const [viewType, setViewType] = useAtom(viewTypeAtom);
const [lensSize, setLensSize] = useAtom(lensSizeAtom);
const t = useAtomValue(translationAtom);
useEffect(() => {
if (!localStorage.getItem("zoomAmount")) {
@ -52,11 +54,13 @@ const ImageOptions = ({
<div className="flex flex-col justify-center gap-5 overflow-auto p-5">
<button className="btn btn-primary" onClick={resetImagePaths}>
Reset Image
{t("APP.INFOS.IMAGE_OPTIONS.RESET")}
</button>
<div className="flex flex-row items-center gap-2">
<p className="text-sm font-medium">Lens View</p>
<p className="text-sm font-medium">
{t("APP.INFOS.IMAGE_OPTIONS.LENS_VIEW")}
</p>
<input
type="checkbox"
className="toggle"
@ -65,11 +69,15 @@ const ImageOptions = ({
setViewType(e.target.checked ? "slider" : "lens");
}}
/>
<p className="text-sm font-medium">Slider View</p>
<p className="text-sm font-medium">
{t("APP.INFOS.IMAGE_OPTIONS.SLIDER_VIEW")}
</p>
</div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">Zoom Amount ({zoomAmount}%)</p>
<p className="text-sm font-medium">
{t("APP.INFOS.IMAGE_OPTIONS.ZOOM_AMOUNT")} ({zoomAmount}%)
</p>
<input
type="range"
min="100"
@ -85,7 +93,9 @@ const ImageOptions = ({
</div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">Lens Size ({lensSize / 10})</p>
<p className="text-sm font-medium">
{t("APP.INFOS.IMAGE_OPTIONS.LENS_SIZE")} ({lensSize / 10})
</p>
<input
type="range"
min="20"

View File

@ -1,6 +1,8 @@
import React, { CSSProperties, useEffect, useMemo } from "react";
import Spinner from "../../icons/Spinner";
import Logo from "@/components/icons/Logo";
import { useAtomValue } from "jotai";
import { translationAtom } from "@/atoms/translations-atom";
function ProgressBar({
progress,
@ -14,6 +16,7 @@ function ProgressBar({
batchMode: boolean;
}) {
const [batchProgress, setBatchProgress] = React.useState(0);
const t = useAtomValue(translationAtom);
useEffect(() => {
const progressString = progress.trim().replace(/\n/g, "");
@ -43,7 +46,8 @@ function ProgressBar({
<div className="flex flex-col items-center gap-2 rounded-btn bg-base-100/50 p-4 backdrop-blur-lg">
<Logo className="spinner h-12 w-12" />
<p className="rounded-full px-2 pb-2 font-bold">
{batchMode && `Batch Upscayl In Progress: ${batchProgress}`}
{batchMode &&
`${t("APP.INFOS.PROGRESS_BAR.IN_PROGRESS")} ${batchProgress}`}
</p>
<div className="flex flex-col items-center gap-1">
{progress !== "Hold on..." ? (
@ -57,11 +61,11 @@ function ProgressBar({
<p className="text-sm font-bold">{progress}</p>
)}
<p className="animate-pulse rounded-full px-2 pb-3 text-xs font-medium text-neutral-content/50">
Doing the Upscayl magic...
{t("APP.INFOS.PROGRESS_BAR.PROGRESS_CATCHY")}
</p>
</div>
<button onClick={stopHandler} className="btn btn-outline">
STOP
{t("APP.INFOS.PROGRESS_BAR.STOP")}
</button>
</div>
</div>

View File

@ -1,12 +1,16 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
function ResetButton(props) {
const t = useAtomValue(translationAtom);
return (
<button
className="animate bg-gradient-blue absolute top-1 right-1 z-10 rounded-full py-2 px-4 text-white opacity-30 hover:opacity-100"
className="animate bg-gradient-blue absolute right-1 top-1 z-10 rounded-full px-4 py-2 text-white opacity-30 hover:opacity-100"
onClick={props.resetImagePaths}
>
Reset
{t("APP.INFOS.RESET")}
</button>
);
}

View File

@ -1,22 +1,29 @@
import { translationAtom } from "@/atoms/translations-atom";
import { useAtomValue } from "jotai";
import React from "react";
function RightPaneInfo({ version, batchMode }) {
const t = useAtomValue(translationAtom);
return (
<div className="flex flex-col items-center bg-base-200 p-4 rounded-btn">
<div className="flex flex-col items-center rounded-btn bg-base-200 p-4">
<p className="pb-1 text-lg font-semibold">
Select {batchMode ? "a Folder" : "an Image"} to Upscayl
{batchMode
? t("APP.INFOS.RIGHT_PANE_INFO.SELECT_FOLDER")
: t("APP.INFOS.RIGHT_PANE_INFO.SELECT_IMAGE")}
</p>
{batchMode ? (
<p className="w-full pb-5 text-center md:w-96 text-base-content/80">
Make sure that the folder doesn't contain anything except PNG, JPG,
JPEG & WEBP images.
<p className="w-full pb-5 text-center text-base-content/80 md:w-96">
{t("APP.INFOS.RIGHT_PANE_INFO.NOTE_SPECIFIC_FORMATS_IN_FOLDER")}
</p>
) : (
<p className="w-full pb-5 text-center md:w-96 text-base-content/80">
Select or drag and drop a PNG, JPG, JPEG or WEBP image.
<p className="w-full pb-5 text-center text-base-content/80 md:w-96">
{t("APP.INFOS.RIGHT_PANE_INFO.SELECT_IMAGES")}
</p>
)}
<p className="text-sm badge badge-primary">Upscayl v{version}</p>
<p className="badge badge-primary text-sm">
{t("APP.INFOS.RIGHT_PANE_INFO.APP_VERSION", { version })}
</p>
</div>
);
}

View File

@ -1,5 +1,217 @@
{
"settings": {
"language": "Change Language"
"APP": {
"TITLE": "Upscayl",
"INTRO": "Introducing Upscayl Cloud!",
"HEADER": {
"GITHUB_STAR_TT_INFO": "Star us on GitHub 😁",
"APP_INFO": "AI Image Upscaler"
},
"FOOTER": {
"NEWS_TITLE": "UPSCAYL NEWS",
"COPYRIGHT": "Copyright ©",
"BY": "By ",
"APP_TEAM": "The Upscayl Team"
},
"SETTINGS": {
"TITLE": "SETTINGS",
"CHANGE_LANG": "Change Language"
},
"INFOS": {
"IMAGE_PROCESSING": {
"START": "Processing the image...",
"SCALE_CONVERT": "Scaling and converting image...",
"WAIT": "Hold on...",
"SUCCESS": "Upscayl Successful!",
"BATCH": {
"SELECT": "Selected folder:",
"DONE": "All done!",
"OPEN_DONE_FOLDER": "Open Upscayled Folder"
}
},
"COMPARISION": {
"SLIDER_ORIGINAL": "Original",
"SLIDER_PROCESSED": "Upscayled"
},
"IMAGE_COMPRESSION": {
"TITLE": "Image Compression ({compression}%)",
"LOSSLESS_TIP": "PNG compression is lossless, so it might not reduce the file size significantly and higher compression values might affect the performance. JPG and WebP compression is lossy."
},
"CUSTOM_MODELS": {
"ADD": "ADD CUSTOM MODELS",
"SELECT_FOLDER": "Select Folder",
"INFO": "You can add your own models easily. For more details:",
"LINK_TEXT": "Custom Models Repository"
},
"CUSTOM_INPUT_RESOLUTION": {
"WIDTH": "CUSTOM OUTPUT WIDTH",
"RESTART": "REQUIRES RESTART",
"CUSTOM_WIDTH_TIP": "Use a custom width for the output images. The height will be adjusted automatically. Enabling this will override the scale setting."
},
"DONATE": {
"IF_LIKED": "If you like what we do :)",
"DONATE": "💎 DONATE"
},
"GPU_ID_INPUT": {
"ID": "GPU ID",
"READ_DOCS": "Please read the Upscayl Documentation for more information.",
"ENABLE_PERF_MODE": "Enable performance mode on Windows for better results."
},
"IMAGE_FORMAT": {
"SAVE_AS": "SAVE IMAGE AS",
"PNG": "PNG",
"JPG": "JPG",
"WEBP": "WEBP"
},
"IMAGE_SCALE": {
"TITLE": "Image Scale",
"TITLE_CAPS": "IMAGE SCALE",
"SCALES_TIMES": "({scale}X)",
"AI_UPSCALE_RESIZE_INFO": "Anything above 4X (except 16X Double Upscayl) only resizes the image and does not use AI upscaling."
},
"LOG_AREA": {
"ON_COPY": "COPIED ✅",
"COPY": "COPY LOGS 📋",
"NO_LOGS": "No logs to show"
},
"OVERWRITE_TOGGLE": {
"OW_PREV": "OVERWRITE PREVIOUS UPSCALE",
"OW_TIP": "If enabled, Upscayl will process the image again instead of loading it directly."
},
"RESET_SETTINGS": {
"TITLE": "RESET UPSCAYL",
"ON_RESET": "Upscayl has been reset. Please restart the app."
},
"SAVE_OUTPUT_FOLDER": {
"TITLE": "SAVE OUTPUT FOLDER",
"DESC": "If enabled, the output folder will be remembered between sessions."
},
"THEME": {
"TITLE": "UPSCAYL THEME"
},
"LANGUAGE": {
"TITLE": "UPSCAYL LANGUAGE"
},
"CUSTOM_TILE_SIZE": {
"TITLE": "CUSTOM TILE SIZE",
"DESC": "Use a custom tile size for segmenting the image. This can help process images faster by reducing the number of tiles generated."
},
"TURN_OFF_NOTIFICATIONS": {
"TITLE": "TURN OFF NOTIFICATIONS",
"DESC": "If enabled, Upscayl will not send any system notifications on success or failure."
},
"DIALOG_BOX": {
"CLOSE": "Close"
},
"LEFT_PANE_PROCESS": {
"BATCH": {
"TITLE": "Batch Upscayl",
"TT_INFO": "This will let you Upscayl all files in a folder at once"
},
"STEP_1": {
"TITLE": "Step 1",
"BATCH_YES": "Select Folder",
"BATCH_NO": "Select Image"
},
"STEP_2": {
"TITLE": "Step 2",
"SELECT_MODEL": "Select Model"
},
"DOUBLE_UPSCAYL": {
"TITLE": "Double Upscayl",
"TT_INFO": "Enable this option to run upscayl twice on an image. Note that this may cause a significant increase in processing time and possibly performance issues for scales greater than 4X."
},
"STEP_3": {
"TITLE": "Step 3",
"MACOS_RESTRICTION_ALERT": "Due to MacOS App Store security restrictions, Upscayl requires you to select an output folder everytime you start it.\n\nTo avoid this, you can permanently save a default output folder in the Upscayl 'Settings' tab.",
"NOT_SELECTED": "Not Selected",
"DEFAULT_IMG_PATH": "Defaults to Image's path",
"DEFAULT_FOLDER_PATH": "Defaults to Folder's path",
"SET_OUTPUT_FOLDER": "Set Output Folder"
},
"STEP_4": {
"TITLE": "Step 4",
"UPSCAYL_FROM": "Upscayl from ",
"UPSCAYL_TO": " to ",
"FOLDER_ALERT": "Please select an output folder first",
"PROCESS_START": "Upscayl",
"PROCESS_IN_PROGRESS": "Upscayling⏳"
}
},
"IMAGE_OPTIONS": {
"RESET": "Reset Image",
"LENS_VIEW": "Lens View",
"SLIDER_VIEW": "Slider View",
"ZOOM_AMOUNT": "Zoom Amount",
"LENS_SIZE": "Lens Size"
},
"PROGRESS_BAR": {
"IN_PROGRESS": "Batch Upscayl In Progress:",
"PROGRESS_CATCHY": "Doing the Upscayl magic...",
"STOP": "STOP"
},
"RESET": "Reset",
"RIGHT_PANE_INFO": {
"SELECT_FOLDER": "Select a Folder to Upscayl",
"SELECT_IMAGE": "Select an Image to Upscayl",
"NOTE_SPECIFIC_FORMATS_IN_FOLDER": "Make sure that the folder doesn't contain anything except PNG, JPG, JPEG & WEBP images.",
"SELECT_IMAGES": "Select or drag and drop a PNG, JPG, JPEG or WEBP image.",
"APP_VERSION": "Upscayl v{version}"
}
},
"ERRORS": {
"GPU_ERROR": {
"TITLE": "GPU Error",
"DESC": "Ran into an issue with the GPU. Please read the docs for troubleshooting! ({data})"
},
"COPY_ERROR": {
"TITLE": "Copy Error",
"DESC": ""
},
"OPEN_DOCS": "Open Docs",
"TROUBLESHOOT": "Troubleshoot",
"READ_WRITE_ERROR": {
"TITLE": "Read/Write Error",
"DESC": "Make sure that the path is correct and you have proper read/write permissions \n({data})"
},
"TILE_SIZE_ERROR": {
"TITLE": "Error",
"DESC": "The tile size is wrong. Please change the tile size in the settings or set to 0 ({data})"
},
"EXCEPTION_ERROR": {
"TITLE": "Exception Error",
"DESC": "Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help."
},
"GENERIC_ERROR": {
"TITLE": "Error"
},
"INVALID_IMAGE_ERROR": {
"TITLE": "Invalid Image",
"DESC": "Please select an image with a valid extension like PNG, JPG, JPEG, or WEBP.",
"DRAG_DESC": "Please drag and drop an image"
},
"NO_IMAGE_ERROR": {
"TITLE": "No image selected",
"DESC": "Please select an image to upscale"
},
"IMAGE_SCALE_WARN": {
"PERF_ISSUE": "Anything above 5X may cause performance issues on some devices!",
"PERF_ISSUE_DEVICE": "This may cause performance issues on some devices!"
},
"ISSUE_CHECK": {
"TITLE": "Having issues?",
"GET_HELP": "🙏 GET HELP",
"EMAIL_DEV": "📧 EMAIL DEVELOPER"
}
},
"UPSCAYL_CLOUD": {
"COMING_SOON": "Coming soon!",
"CATCHY_PHRASE_1": "No more errors, hardware issues, quality compromises or long loading times!",
"CATCHY_PHRASE_2": "🌐 Upscayl anywhere, anytime, any device\n☁ No Graphics Card or hardware required\n👩 Face Enhancement\n🦋 10+ models to choose from\n🏎 5x faster than Upscayl Desktop\n🎞 Video Upscaling\n💰 Commercial Usage\n😴 Upscayl while you sleep",
"ALREADY_REGISTERED": "Thank you {name}! It seems that your email has already been registered :D If that's not the case, please try again.",
"ADD_SUCCESS": "Thank you for joining the waitlist! We will notify you when Upscayl Cloud is ready for you.",
"INCORRECT_FIELDS": "Please fill in all the fields correctly.",
"JOIN_WAITLIST": "Join the waitlist",
"DONT_SHOW_AGAIN": "DON'T SHOW AGAIN"
}
}
}

217
renderer/locales/ja.json Normal file
View File

@ -0,0 +1,217 @@
{
"APP": {
"TITLE": "Upscayl",
"INTRO": "Upscayl Cloudの紹介",
"HEADER": {
"GITHUB_STAR_TT_INFO": "GitHubでスターをつけてください 😁",
"APP_INFO": "AI画像アップスケーラー"
},
"FOOTER": {
"NEWS_TITLE": "UPSCAYLニュース",
"COPYRIGHT": "著作権 ©",
"BY": "作成者 ",
"APP_TEAM": "Upscaylチーム"
},
"SETTINGS": {
"TITLE": "設定",
"CHANGE_LANG": "言語を変更"
},
"INFOS": {
"IMAGE_PROCESSING": {
"START": "画像を処理中...",
"SCALE_CONVERT": "画像をスケールして変換中...",
"WAIT": "少々お待ちください...",
"SUCCESS": "Upscaylに成功しました",
"BATCH": {
"SELECT": "選択されたフォルダ:",
"DONE": "すべて完了!",
"OPEN_DONE_FOLDER": "Upscayledフォルダを開く"
}
},
"COMPARISION": {
"SLIDER_ORIGINAL": "オリジナル",
"SLIDER_PROCESSED": "Upscayled"
},
"IMAGE_COMPRESSION": {
"TITLE": "画像圧縮 ({compression}%)",
"LOSSLESS_TIP": "PNG圧縮は無損失のため、ファイルサイズが大幅に減らない可能性があります。圧縮率を高くするとパフォーマンスに影響が出る場合があります。JPGとWebPの圧縮は有損です。"
},
"CUSTOM_MODELS": {
"ADD": "カスタムモデルを追加",
"SELECT_FOLDER": "フォルダを選択",
"INFO": "簡単に独自のモデルを追加できます。詳細については:",
"LINK_TEXT": "カスタムモデルリポジトリ"
},
"CUSTOM_INPUT_RESOLUTION": {
"WIDTH": "カスタム出力幅",
"RESTART": "再起動が必要",
"CUSTOM_WIDTH_TIP": "出力画像のカスタム幅を使用します。高さは自動的に調整されます。これを有効にすると、スケール設定が無効になります。"
},
"DONATE": {
"IF_LIKED": "気に入っていただけたら :)",
"DONATE": "💎 寄付する"
},
"GPU_ID_INPUT": {
"ID": "GPU ID",
"READ_DOCS": "詳細については、Upscaylのドキュメントをお読みください。",
"ENABLE_PERF_MODE": "Windowsでパフォーマンスモードを有効にして、より良い結果を得る。"
},
"IMAGE_FORMAT": {
"SAVE_AS": "画像を保存",
"PNG": "PNG",
"JPG": "JPG",
"WEBP": "WEBP"
},
"IMAGE_SCALE": {
"TITLE": "画像スケール",
"TITLE_CAPS": "画像スケール",
"SCALES_TIMES": "({scale}倍)",
"AI_UPSCALE_RESIZE_INFO": "4倍以上16倍のDouble Upscaylを除くは、画像のサイズ変更のみで、AIアップスケールは行われません。"
},
"LOG_AREA": {
"ON_COPY": "コピーされました ✅",
"COPY": "ログをコピー 📋",
"NO_LOGS": "表示するログがありません"
},
"OVERWRITE_TOGGLE": {
"OW_PREV": "以前のアップスケールを上書き",
"OW_TIP": "有効にすると、Upscaylは画像を再処理し、直接読み込むのではなく処理します。"
},
"RESET_SETTINGS": {
"TITLE": "UPSCAYLをリセット",
"ON_RESET": "Upscaylがリセットされました。アプリを再起動してください。"
},
"SAVE_OUTPUT_FOLDER": {
"TITLE": "出力フォルダを保存",
"DESC": "有効にすると、セッション間で出力フォルダが記憶されます。"
},
"THEME": {
"TITLE": "UPSCAYL テーマ"
},
"LANGUAGE": {
"TITLE": "UPSCAYL 言語"
},
"CUSTOM_TILE_SIZE": {
"TITLE": "カスタムタイルサイズ",
"DESC": "画像を分割するためのカスタムタイルサイズを使用します。これにより、生成されるタイルの数を減らすことで、画像処理を高速化できます。"
},
"TURN_OFF_NOTIFICATIONS": {
"TITLE": "通知をオフにする",
"DESC": "有効にすると、Upscaylは成功や失敗時のシステム通知を送信しません。"
},
"DIALOG_BOX": {
"CLOSE": "閉じる"
},
"LEFT_PANE_PROCESS": {
"BATCH": {
"TITLE": "バッチUpscayl",
"TT_INFO": "これにより、フォルダ内のすべてのファイルを一度にUpscaylできます"
},
"STEP_1": {
"TITLE": "ステップ1",
"BATCH_YES": "フォルダを選択",
"BATCH_NO": "画像を選択"
},
"STEP_2": {
"TITLE": "ステップ2",
"SELECT_MODEL": "モデルを選択"
},
"DOUBLE_UPSCAYL": {
"TITLE": "Double Upscayl",
"TT_INFO": "このオプションを有効にすると、画像に対して2回Upscaylを実行します。4倍以上のスケールでは、処理時間が大幅に増加し、パフォーマンス問題が発生する可能性があります。"
},
"STEP_3": {
"TITLE": "ステップ3",
"MACOS_RESTRICTION_ALERT": "MacOS App Storeのセキュリティ制限のため、Upscaylを起動するたびに出力フォルダを選択する必要があります。\n\nこれを避けるために、Upscaylの「設定」タブでデフォルトの出力フォルダを永続的に保存できます。",
"NOT_SELECTED": "未選択",
"DEFAULT_IMG_PATH": "画像のパスにデフォルト設定",
"DEFAULT_FOLDER_PATH": "フォルダのパスにデフォルト設定",
"SET_OUTPUT_FOLDER": "出力フォルダを設定"
},
"STEP_4": {
"TITLE": "ステップ4",
"UPSCAYL_FROM": "Upscaylから ",
"UPSCAYL_TO": " まで",
"FOLDER_ALERT": "まず出力フォルダを選択してください",
"PROCESS_START": "Upscayl",
"PROCESS_IN_PROGRESS": "Upscayling⏳"
}
},
"IMAGE_OPTIONS": {
"RESET": "画像をリセット",
"LENS_VIEW": "レンズビュー",
"SLIDER_VIEW": "スライダービュー",
"ZOOM_AMOUNT": "ズーム量",
"LENS_SIZE": "レンズサイズ"
},
"PROGRESS_BAR": {
"IN_PROGRESS": "バッチUpscayl進行中:",
"PROGRESS_CATCHY": "Upscaylの魔法を実行中...",
"STOP": "停止"
},
"RESET": "リセット",
"RIGHT_PANE_INFO": {
"SELECT_FOLDER": "Upscaylするフォルダを選択",
"SELECT_IMAGE": "Upscaylする画像を選択",
"NOTE_SPECIFIC_FORMATS_IN_FOLDER": "フォルダにはPNG、JPG、JPEG、およびWEBP画像以外を含めないでください。",
"SELECT_IMAGES": "PNG、JPG、JPEGまたはWEBP画像を選択するか、ドラッグドロップしてください。",
"APP_VERSION": "Upscayl v{version}"
}
},
"ERRORS": {
"GPU_ERROR": {
"TITLE": "GPUエラー",
"DESC": "GPUに問題が発生しました。トラブルシューティングについてはドキュメントをお読みください ({data})"
},
"COPY_ERROR": {
"TITLE": "コピーエラー",
"DESC": ""
},
"OPEN_DOCS": "ドキュメントを開く",
"TROUBLESHOOT": "トラブルシューティング",
"READ_WRITE_ERROR": {
"TITLE": "読み書きエラー",
"DESC": "パスが正しく、適切な読み書き権限があることを確認してください \n({data})"
},
"TILE_SIZE_ERROR": {
"TITLE": "エラー",
"DESC": "タイルサイズが間違っています。設定でタイルサイズを変更するか、0に設定してください ({data})"
},
"EXCEPTION_ERROR": {
"TITLE": "例外エラー",
"DESC": "Upscaylでエラーが発生しました。おそらく、Upscaylバイナリがコマンドを正しく実行できなかった可能性があります。ログを確認して情報を得てください。さらに支援が必要な場合は、UpscaylのGitHubリポジトリに問題を投稿できます。"
},
"GENERIC_ERROR": {
"TITLE": "エラー"
},
"INVALID_IMAGE_ERROR": {
"TITLE": "無効な画像",
"DESC": "PNG、JPG、JPEG、またはWEBPの有効な拡張子を持つ画像を選択してください。",
"DRAG_DESC": "画像をドラッグ&ドロップしてください"
},
"NO_IMAGE_ERROR": {
"TITLE": "画像が選択されていません",
"DESC": "アップスケールする画像を選択してください"
},
"IMAGE_SCALE_WARN": {
"PERF_ISSUE": "5倍以上は一部のデバイスでパフォーマンス問題を引き起こす可能性があります",
"PERF_ISSUE_DEVICE": "一部のデバイスでパフォーマンス問題が発生する可能性があります!"
},
"ISSUE_CHECK": {
"TITLE": "問題がありますか?",
"GET_HELP": "🙏 助けを得る",
"EMAIL_DEV": "📧 開発者にメール"
}
},
"UPSCAYL_CLOUD": {
"COMING_SOON": "近日公開!",
"CATCHY_PHRASE_1": "エラー、ハードウェアの問題、品質の妥協、長い読み込み時間はもうありません!",
"CATCHY_PHRASE_2": "🌐 どこでも、いつでも、どんなデバイスでもUpscayl\n☁ グラフィックカードやハードウェア不要\n👩 顔の強化\n🦋 10以上のモデルから選択可能\n🏎 Upscaylデスクトップより5倍速い\n🎞 ビデオアップスケーリング\n💰 商用利用可\n😴 眠っている間にUpscayl",
"ALREADY_REGISTERED": "ありがとう{name}!あなたのメールはすでに登録されているようです :D もしそうでない場合は、もう一度お試しください。",
"ADD_SUCCESS": "待機リストへのご参加ありがとうございますUpscayl Cloudが準備でき次第ご連絡いたします。",
"INCORRECT_FIELDS": "すべてのフィールドに正しく記入してください。",
"JOIN_WAITLIST": "待機リストに参加する",
"DONT_SHOW_AGAIN": "再び表示しない"
}
}
}

View File

@ -0,0 +1,223 @@
// !!!!!!!!!!!!!!!!!!!!DO NOT DELETE THIS FILE!!!!!!!!!!!!!!
// Copy this to a new file
// Name the file {language}.json like en-US.json, ru-RU.json, etc
// Replace the english strings present below with relevant languages
// !!!!!!!!!!!!!!!!!!!!!KEEP ANYTHING PRESENT WITHIN FLOWER BRACES {variable} - THEY ARE VARIABLES!!!!!!!!!!!!!!!!!!!!!!!
// Delete these comments starting with "//" as json format does not accept comments
{
"APP": {
"TITLE": "Upscayl",
"INTRO": "Introducing Upscayl Cloud!",
"HEADER": {
"GITHUB_STAR_TT_INFO": "Star us on GitHub 😁",
"APP_INFO": "AI Image Upscaler"
},
"FOOTER": {
"NEWS_TITLE": "UPSCAYL NEWS",
"COPYRIGHT": "Copyright ©",
"BY": "By ",
"APP_TEAM": "The Upscayl Team"
},
"SETTINGS": {
"TITLE": "SETTINGS",
"CHANGE_LANG": "Change Language"
},
"INFOS": {
"IMAGE_PROCESSING": {
"START": "Processing the image...",
"SCALE_CONVERT": "Scaling and converting image...",
"WAIT": "Hold on...",
"SUCCESS": "Upscayl Successful!",
"BATCH": {
"SELECT": "Selected folder:",
"DONE": "All done!",
"OPEN_DONE_FOLDER": "Open Upscayled Folder"
}
},
"COMPARISION": {
"SLIDER_ORIGINAL": "Original",
"SLIDER_PROCESSED": "Upscayled"
},
"IMAGE_COMPRESSION": {
"TITLE": "Image Compression ({compression}%)",
"LOSSLESS_TIP": "PNG compression is lossless, so it might not reduce the file size significantly and higher compression values might affect the performance. JPG and WebP compression is lossy."
},
"CUSTOM_MODELS": {
"ADD": "ADD CUSTOM MODELS",
"SELECT_FOLDER": "Select Folder",
"INFO": "You can add your own models easily. For more details:",
"LINK_TEXT": "Custom Models Repository"
},
"CUSTOM_INPUT_RESOLUTION": {
"WIDTH": "CUSTOM OUTPUT WIDTH",
"RESTART": "REQUIRES RESTART",
"CUSTOM_WIDTH_TIP": "Use a custom width for the output images. The height will be adjusted automatically. Enabling this will override the scale setting."
},
"DONATE": {
"IF_LIKED": "If you like what we do :)",
"DONATE": "💎 DONATE"
},
"GPU_ID_INPUT": {
"ID": "GPU ID",
"READ_DOCS": "Please read the Upscayl Documentation for more information.",
"ENABLE_PERF_MODE": "Enable performance mode on Windows for better results."
},
"IMAGE_FORMAT": {
"SAVE_AS": "SAVE IMAGE AS",
"PNG": "PNG",
"JPG": "JPG",
"WEBP": "WEBP"
},
"IMAGE_SCALE": {
"TITLE": "Image Scale",
"TITLE_CAPS": "IMAGE SCALE",
"SCALES_TIMES": "({scale}X)",
"AI_UPSCALE_RESIZE_INFO": "Anything above 4X (except 16X Double Upscayl) only resizes the image and does not use AI upscaling."
},
"LOG_AREA": {
"ON_COPY": "COPIED ✅",
"COPY": "COPY LOGS 📋",
"NO_LOGS": "No logs to show"
},
"OVERWRITE_TOGGLE": {
"OW_PREV": "OVERWRITE PREVIOUS UPSCALE",
"OW_TIP": "If enabled, Upscayl will process the image again instead of loading it directly."
},
"RESET_SETTINGS": {
"TITLE": "RESET UPSCAYL",
"ON_RESET": "Upscayl has been reset. Please restart the app."
},
"SAVE_OUTPUT_FOLDER": {
"TITLE": "SAVE OUTPUT FOLDER",
"DESC": "If enabled, the output folder will be remembered between sessions."
},
"THEME": {
"TITLE": "UPSCAYL THEME"
},
"LANGUAGE": {
"TITLE": "UPSCAYL LANGUAGE"
},
"CUSTOM_TILE_SIZE": {
"TITLE": "CUSTOM TILE SIZE",
"DESC": "Use a custom tile size for segmenting the image. This can help process images faster by reducing the number of tiles generated."
},
"TURN_OFF_NOTIFICATIONS": {
"TITLE": "TURN OFF NOTIFICATIONS",
"DESC": "If enabled, Upscayl will not send any system notifications on success or failure."
},
"DIALOG_BOX": {
"CLOSE": "Close"
},
"LEFT_PANE_PROCESS": {
"BATCH": {
"TITLE": "Batch Upscayl",
"TT_INFO": "This will let you Upscayl all files in a folder at once"
},
"STEP_1": {
"TITLE": "Step 1",
"BATCH_YES": "Select Folder",
"BATCH_NO": "Select Image"
},
"STEP_2": {
"TITLE": "Step 2",
"SELECT_MODEL": "Select Model"
},
"DOUBLE_UPSCAYL": {
"TITLE": "Double Upscayl",
"TT_INFO": "Enable this option to run upscayl twice on an image. Note that this may cause a significant increase in processing time and possibly performance issues for scales greater than 4X."
},
"STEP_3": {
"TITLE": "Step 3",
"MACOS_RESTRICTION_ALERT": "Due to MacOS App Store security restrictions, Upscayl requires you to select an output folder everytime you start it.\n\nTo avoid this, you can permanently save a default output folder in the Upscayl 'Settings' tab.",
"NOT_SELECTED": "Not Selected",
"DEFAULT_IMG_PATH": "Defaults to Image's path",
"DEFAULT_FOLDER_PATH": "Defaults to Folder's path",
"SET_OUTPUT_FOLDER": "Set Output Folder"
},
"STEP_4": {
"TITLE": "Step 4",
"UPSCAYL_FROM": "Upscayl from ",
"UPSCAYL_TO": " to ",
"FOLDER_ALERT": "Please select an output folder first",
"PROCESS_START": "Upscayl",
"PROCESS_IN_PROGRESS": "Upscayling⏳"
}
},
"IMAGE_OPTIONS": {
"RESET": "Reset Image",
"LENS_VIEW": "Lens View",
"SLIDER_VIEW": "Slider View",
"ZOOM_AMOUNT": "Zoom Amount",
"LENS_SIZE": "Lens Size"
},
"PROGRESS_BAR": {
"IN_PROGRESS": "Batch Upscayl In Progress:",
"PROGRESS_CATCHY": "Doing the Upscayl magic...",
"STOP": "STOP"
},
"RESET": "Reset",
"RIGHT_PANE_INFO": {
"SELECT_FOLDER": "Select a Folder to Upscayl",
"SELECT_IMAGE": "Select an Image to Upscayl",
"NOTE_SPECIFIC_FORMATS_IN_FOLDER": "Make sure that the folder doesn't contain anything except PNG, JPG, JPEG & WEBP images.",
"SELECT_IMAGES": "Select or drag and drop a PNG, JPG, JPEG or WEBP image.",
"APP_VERSION": "Upscayl v{version}"
}
},
"ERRORS": {
"GPU_ERROR": {
"TITLE": "GPU Error",
"DESC": "Ran into an issue with the GPU. Please read the docs for troubleshooting! ({data})"
},
"COPY_ERROR": {
"TITLE": "Copy Error",
"DESC": ""
},
"OPEN_DOCS": "Open Docs",
"TROUBLESHOOT": "Troubleshoot",
"READ_WRITE_ERROR": {
"TITLE": "Read/Write Error",
"DESC": "Make sure that the path is correct and you have proper read/write permissions \n({data})"
},
"TILE_SIZE_ERROR": {
"TITLE": "Error",
"DESC": "The tile size is wrong. Please change the tile size in the settings or set to 0 ({data})"
},
"EXCEPTION_ERROR": {
"TITLE": "Exception Error",
"DESC": "Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help."
},
"GENERIC_ERROR": {
"TITLE": "Error"
},
"INVALID_IMAGE_ERROR": {
"TITLE": "Invalid Image",
"DESC": "Please select an image with a valid extension like PNG, JPG, JPEG, or WEBP.",
"DRAG_DESC": "Please drag and drop an image"
},
"NO_IMAGE_ERROR": {
"TITLE": "No image selected",
"DESC": "Please select an image to upscale"
},
"IMAGE_SCALE_WARN": {
"PERF_ISSUE": "Anything above 5X may cause performance issues on some devices!",
"PERF_ISSUE_DEVICE": "This may cause performance issues on some devices!"
},
"ISSUE_CHECK": {
"TITLE": "Having issues?",
"GET_HELP": "🙏 GET HELP",
"EMAIL_DEV": "📧 EMAIL DEVELOPER"
}
},
"UPSCAYL_CLOUD": {
"COMING_SOON": "Coming soon!",
"CATCHY_PHRASE_1": "No more errors, hardware issues, quality compromises or long loading times!",
"CATCHY_PHRASE_2": "🌐 Upscayl anywhere, anytime, any device\n☁ No Graphics Card or hardware required\n👩 Face Enhancement\n🦋 10+ models to choose from\n🏎 5x faster than Upscayl Desktop\n🎞 Video Upscaling\n💰 Commercial Usage\n😴 Upscayl while you sleep",
"ALREADY_REGISTERED": "Thank you {name}! It seems that your email has already been registered :D If that's not the case, please try again.",
"ADD_SUCCESS": "Thank you for joining the waitlist! We will notify you when Upscayl Cloud is ready for you.",
"INCORRECT_FIELDS": "Please fill in all the fields correctly.",
"JOIN_WAITLIST": "Join the waitlist",
"DONT_SHOW_AGAIN": "DON'T SHOW AGAIN"
}
}
}

View File

@ -1,5 +1,217 @@
{
"settings": {
"language": "Меняй язык"
"APP": {
"TITLE": "Upscayl",
"INTRO": "Представляем Upscayl Cloud!",
"HEADER": {
"GITHUB_STAR_TT_INFO": "Поставьте нам звезду на GitHub 😁",
"APP_INFO": "Улучшение изображений с помощью ИИ"
},
"FOOTER": {
"NEWS_TITLE": "НОВОСТИ UPSCAYL",
"COPYRIGHT": "Авторское право ©",
"BY": "От ",
"APP_TEAM": "Команда Upscayl"
},
"SETTINGS": {
"TITLE": "НАСТРОЙКИ",
"CHANGE_LANG": "Изменить язык"
},
"INFOS": {
"IMAGE_PROCESSING": {
"START": "Обработка изображения...",
"SCALE_CONVERT": "Масштабирование и конвертация изображения...",
"WAIT": "Подождите...",
"SUCCESS": "Upscayl выполнен успешно!",
"BATCH": {
"SELECT": "Выбранная папка:",
"DONE": "Все готово!",
"OPEN_DONE_FOLDER": "Открыть папку с Upscayl"
}
},
"COMPARISION": {
"SLIDER_ORIGINAL": "Оригинал",
"SLIDER_PROCESSED": "Upscayl"
},
"IMAGE_COMPRESSION": {
"TITLE": "Сжатие изображения ({compression}%)",
"LOSSLESS_TIP": "Сжатие PNG без потерь, поэтому размер файла может не уменьшиться значительно, а более высокие значения сжатия могут повлиять на производительность. Сжатие JPG и WebP с потерями."
},
"CUSTOM_MODELS": {
"ADD": "ДОБАВИТЬ СОБСТВЕННЫЕ МОДЕЛИ",
"SELECT_FOLDER": "Выбрать папку",
"INFO": "Вы можете легко добавить свои модели. Для получения дополнительной информации:",
"LINK_TEXT": "Репозиторий пользовательских моделей"
},
"CUSTOM_INPUT_RESOLUTION": {
"WIDTH": "ПОЛЬЗОВАТЕЛЬСКАЯ ШИРИНА ВЫВОДА",
"RESTART": "ТРЕБУЕТСЯ ПЕРЕЗАГРУЗКА",
"CUSTOM_WIDTH_TIP": "Используйте пользовательскую ширину для выходных изображений. Высота будет скорректирована автоматически. Включение этой опции отменит настройку масштаба."
},
"DONATE": {
"IF_LIKED": "Если вам нравится то, что мы делаем :)",
"DONATE": "💎 ПОЖЕРТВОВАТЬ"
},
"GPU_ID_INPUT": {
"ID": "ID GPU",
"READ_DOCS": "Пожалуйста, ознакомьтесь с документацией Upscayl для получения дополнительной информации.",
"ENABLE_PERF_MODE": "Включите режим производительности в Windows для улучшения результатов."
},
"IMAGE_FORMAT": {
"SAVE_AS": "СОХРАНИТЬ ИЗОБРАЖЕНИЕ КАК",
"PNG": "PNG",
"JPG": "JPG",
"WEBP": "WEBP"
},
"IMAGE_SCALE": {
"TITLE": "Масштаб изображения",
"TITLE_CAPS": "МАСШТАБ ИЗОБРАЖЕНИЯ",
"SCALES_TIMES": "({scale}X)",
"AI_UPSCALE_RESIZE_INFO": "Все, что выше 4X (кроме 16X Double Upscayl), только изменяет размер изображения и не использует улучшение с помощью ИИ."
},
"LOG_AREA": {
"ON_COPY": "СКОПИРОВАНО ✅",
"COPY": "СКОПИРОВАТЬ ЛОГИ 📋",
"NO_LOGS": "Нет логов для отображения"
},
"OVERWRITE_TOGGLE": {
"OW_PREV": "ПЕРЕЗАПИСАТЬ ПРЕДЫДУЩИЙ UPSCALE",
"OW_TIP": "Если включено, Upscayl обработает изображение заново, вместо загрузки его напрямую."
},
"RESET_SETTINGS": {
"TITLE": "СБРОС UPSCAYL",
"ON_RESET": "Upscayl был сброшен. Пожалуйста, перезапустите приложение."
},
"SAVE_OUTPUT_FOLDER": {
"TITLE": "СОХРАНИТЬ ПАПКУ ВЫВОДА",
"DESC": "Если включено, папка вывода будет запомнена между сеансами."
},
"THEME": {
"TITLE": "ТЕМА UPSCAYL"
},
"LANGUAGE": {
"TITLE": "ЯЗЫК UPSCAYL"
},
"CUSTOM_TILE_SIZE": {
"TITLE": "ПОЛЬЗОВАТЕЛЬСКИЙ РАЗМЕР ПЛИТКИ",
"DESC": "Используйте пользовательский размер плитки для сегментации изображения. Это может помочь ускорить обработку изображений за счет уменьшения количества создаваемых плиток."
},
"TURN_OFF_NOTIFICATIONS": {
"TITLE": "ОТКЛЮЧИТЬ УВЕДОМЛЕНИЯ",
"DESC": "Если включено, Upscayl не будет отправлять системные уведомления о успехе или неудаче."
},
"DIALOG_BOX": {
"CLOSE": "Закрыть"
},
"LEFT_PANE_PROCESS": {
"BATCH": {
"TITLE": "Массовый Upscayl",
"TT_INFO": "Это позволит вам обработать все файлы в папке сразу"
},
"STEP_1": {
"TITLE": "Шаг 1",
"BATCH_YES": "Выбрать папку",
"BATCH_NO": "Выбрать изображение"
},
"STEP_2": {
"TITLE": "Шаг 2",
"SELECT_MODEL": "Выбрать модель"
},
"DOUBLE_UPSCAYL": {
"TITLE": "Двойной Upscayl",
"TT_INFO": "Включите эту опцию, чтобы дважды обработать изображение с помощью Upscayl. Обратите внимание, что это может значительно увеличить время обработки и возможные проблемы с производительностью при масштабах больше 4X."
},
"STEP_3": {
"TITLE": "Шаг 3",
"MACOS_RESTRICTION_ALERT": "Из-за ограничений безопасности App Store на MacOS, Upscayl требует, чтобы вы выбирали папку вывода каждый раз при запуске.\n\nЧтобы избежать этого, вы можете навсегда сохранить папку вывода по умолчанию во вкладке 'Настройки' Upscayl.",
"NOT_SELECTED": "Не выбрано",
"DEFAULT_IMG_PATH": "По умолчанию путь изображения",
"DEFAULT_FOLDER_PATH": "По умолчанию путь папки",
"SET_OUTPUT_FOLDER": "Установить папку вывода"
},
"STEP_4": {
"TITLE": "Шаг 4",
"UPSCAYL_FROM": "Upscayl с ",
"UPSCAYL_TO": " до ",
"FOLDER_ALERT": "Пожалуйста, сначала выберите папку вывода",
"PROCESS_START": "Upscayl",
"PROCESS_IN_PROGRESS": "Upscayling⏳"
}
},
"IMAGE_OPTIONS": {
"RESET": "Сбросить изображение",
"LENS_VIEW": "Вид через линзу",
"SLIDER_VIEW": "Просмотр через слайдер",
"ZOOM_AMOUNT": "Увеличение",
"LENS_SIZE": "Размер линзы"
},
"PROGRESS_BAR": {
"IN_PROGRESS": "Массовый Upscayl в процессе:",
"PROGRESS_CATCHY": "Происходит магия Upscayl...",
"STOP": "ОСТАНОВИТЬ"
},
"RESET": "Сброс",
"RIGHT_PANE_INFO": {
"SELECT_FOLDER": "Выберите папку для Upscayl",
"SELECT_IMAGE": "Выберите изображение для Upscayl",
"NOTE_SPECIFIC_FORMATS_IN_FOLDER": "Убедитесь, что в папке нет ничего, кроме изображений форматов PNG, JPG, JPEG и WEBP.",
"SELECT_IMAGES": "Выберите или перетащите изображение формата PNG, JPG, JPEG или WEBP.",
"APP_VERSION": "Upscayl v{version}"
}
},
"ERRORS": {
"GPU_ERROR": {
"TITLE": "Ошибка GPU",
"DESC": "Произошла ошибка с GPU. Пожалуйста, ознакомьтесь с документацией для устранения неполадок! ({data})"
},
"COPY_ERROR": {
"TITLE": "Ошибка копирования",
"DESC": ""
},
"OPEN_DOCS": "Открыть документацию",
"TROUBLESHOOT": "Устранение неполадок",
"READ_WRITE_ERROR": {
"TITLE": "Ошибка чтения/записи",
"DESC": "Убедитесь, что путь указан правильно и у вас есть соответствующие права на чтение/запись \n({data})"
},
"TILE_SIZE_ERROR": {
"TITLE": "Ошибка",
"DESC": "Неправильный размер плитки. Пожалуйста, измените размер плитки в настройках или установите на 0 ({data})"
},
"EXCEPTION_ERROR": {
"TITLE": "Ошибка исключения",
"DESC": "Upscayl столкнулся с ошибкой. Возможно, двоичный файл upscayl не смог правильно выполнить команды. Попробуйте проверить логи, чтобы получить информацию. Вы можете создать запрос в репозитории Upscayl на GitHub для получения дополнительной помощи."
},
"GENERIC_ERROR": {
"TITLE": "Ошибка"
},
"INVALID_IMAGE_ERROR": {
"TITLE": "Недопустимое изображение",
"DESC": "Пожалуйста, выберите изображение с допустимым расширением, таким как PNG, JPG, JPEG или WEBP.",
"DRAG_DESC": "Пожалуйста, перетащите изображение"
},
"NO_IMAGE_ERROR": {
"TITLE": "Изображение не выбрано",
"DESC": "Пожалуйста, выберите изображение для улучшения"
},
"IMAGE_SCALE_WARN": {
"PERF_ISSUE": "Масштабирование более чем на 5X может вызвать проблемы с производительностью на некоторых устройствах!",
"PERF_ISSUE_DEVICE": "Это может вызвать проблемы с производительностью на некоторых устройствах!"
},
"ISSUE_CHECK": {
"TITLE": "Возникли проблемы?",
"GET_HELP": "🙏 ПОЛУЧИТЕ ПОМОЩЬ",
"EMAIL_DEV": "📧 НАПИСАТЬ РАЗРАБОТЧИКУ"
}
},
"UPSCAYL_CLOUD": {
"COMING_SOON": "Скоро будет доступно!",
"CATCHY_PHRASE_1": "Больше никаких ошибок, проблем с оборудованием, компромиссов по качеству или долгих загрузок!",
"CATCHY_PHRASE_2": "🌐 Upscayl везде, в любое время, на любом устройстве\n☁ Не требуется видеокарта или оборудование\n👩 Улучшение лица\n🦋 Более 10 моделей на выбор\n🏎 В 5 раз быстрее, чем Upscayl Desktop\n🎞 Улучшение видео\n💰 Коммерческое использование\n😴 Upscayl, пока вы спите",
"ALREADY_REGISTERED": "Спасибо {name}! Похоже, ваш email уже зарегистрирован :D Если это не так, попробуйте снова.",
"ADD_SUCCESS": "Спасибо за участие в списке ожидания! Мы уведомим вас, когда Upscayl Cloud будет готов для вас.",
"INCORRECT_FIELDS": "Пожалуйста, заполните все поля правильно.",
"JOIN_WAITLIST": "Присоединиться к списку ожидания",
"DONT_SHOW_AGAIN": "БОЛЬШЕ НЕ ПОКАЗЫВАТЬ"
}
}
}

View File

@ -48,10 +48,13 @@ import { ToastAction } from "@/components/ui/toast";
import Logo from "@/components/icons/Logo";
import { sanitizePath } from "@common/sanitize-path";
import getDirectoryFromPath from "@common/get-directory-from-path";
import { translationAtom } from "@/atoms/translations-atom";
const Home = () => {
const allowedFileTypes = ["png", "jpg", "jpeg", "webp"];
const t = useAtomValue(translationAtom);
// LOCAL STATES
const [os, setOs] = useState<"linux" | "mac" | "win" | undefined>(undefined);
const [imagePath, setImagePath] = useState("");
@ -137,20 +140,22 @@ const Home = () => {
const handleErrors = (data: string) => {
if (data.includes("Invalid GPU")) {
toast({
title: "GPU Error",
description: `Ran into an issue with the GPU. Please read the docs for troubleshooting! (${data})`,
title: t("APP.ERRORS.GPU_ERROR.TITLE"),
description: t("APP.ERRORS.GPU_ERROR.DESC", { data }),
action: (
<div className="flex flex-col gap-2">
<ToastAction
altText="Copy Error"
altText={t("APP.ERRORS.COPY_ERROR.TITLE")}
onClick={() => {
navigator.clipboard.writeText(data);
}}
>
Copy Error
{t("APP.ERRORS.COPY_ERROR.TITLE")}
</ToastAction>
<a href="https://docs.upscayl.org/" target="_blank">
<ToastAction altText="Open Docs">Troubleshoot</ToastAction>
<ToastAction altText={t("APP.ERRORS.OPEN_DOCS")}>
{t("APP.ERRORS.TROUBLESHOOT")}
</ToastAction>
</a>
</div>
),
@ -159,8 +164,8 @@ const Home = () => {
} else if (data.includes("write") || data.includes("read")) {
if (batchMode) return;
toast({
title: "Read/Write Error",
description: `Make sure that the path is correct and you have proper read/write permissions \n(${data})`,
title: t("APP.ERRORS.READ_WRITE_ERROR.TITLE"),
description: t("APP.ERRORS.READ_WRITE_ERROR.DESC", { data }),
action: (
<div className="flex flex-col gap-2">
<ToastAction
@ -169,10 +174,12 @@ const Home = () => {
navigator.clipboard.writeText(data);
}}
>
Copy Error
{t("APP.ERRORS.COPY_ERROR.TITLE")}
</ToastAction>
<a href="https://docs.upscayl.org/" target="_blank">
<ToastAction altText="Open Docs">Troubleshoot</ToastAction>
<ToastAction altText={t("APP.ERRORS.OPEN_DOCS")}>
{t("APP.ERRORS.TROUBLESHOOT")}
</ToastAction>
</a>
</div>
),
@ -180,14 +187,14 @@ const Home = () => {
resetImagePaths();
} else if (data.includes("tile size")) {
toast({
title: "Error",
description: `The tile size is wrong. Please change the tile size in the settings or set to 0 (${data})`,
title: t("APP.ERRORS.TILE_SIZE_ERROR.TITLE"),
description: t("APP.ERRORS.TILE_SIZE_ERROR.DESC", { data }),
});
resetImagePaths();
} else if (data.includes("uncaughtException")) {
toast({
title: "Exception Error",
description: `Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help.`,
title: t("APP.ERRORS.EXCEPTION_ERROR.TITLE"),
description: t("APP.ERRORS.EXCEPTION_ERROR.DESC"),
});
resetImagePaths();
}
@ -207,12 +214,12 @@ const Home = () => {
});
// SCALING AND CONVERTING
window.electron.on(COMMAND.SCALING_AND_CONVERTING, (_, data: string) => {
setProgress("Processing the image...");
setProgress(t("APP.INFOS.IMAGE_PROCESSING.START"));
});
// UPSCAYL ERROR
window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => {
toast({
title: "Error",
title: t("APP.ERRORS.GENERIC_ERROR.TITLE"),
description: data,
});
resetImagePaths();
@ -222,9 +229,9 @@ const Home = () => {
if (data.length > 0 && data.length < 10) {
setProgress(data);
} else if (data.includes("converting")) {
setProgress("Scaling and converting image...");
setProgress(t("APP.INFOS.IMAGE_PROCESSING.SCALE_CONVERT"));
} else if (data.includes("Successful")) {
setProgress("Upscayl Successful!");
setProgress(t("APP.INFOS.IMAGE_PROCESSING.SUCCESS"));
}
handleErrors(data);
logit(`🚧 UPSCAYL_PROGRESS: `, data);
@ -232,7 +239,7 @@ const Home = () => {
// FOLDER UPSCAYL PROGRESS
window.electron.on(COMMAND.FOLDER_UPSCAYL_PROGRESS, (_, data: string) => {
if (data.includes("Successful")) {
setProgress("Upscayl Successful!");
setProgress(t("APP.INFOS.IMAGE_PROCESSING.SUCCESS"));
}
if (data.length > 0 && data.length < 10) {
setProgress(data);
@ -371,9 +378,8 @@ const Home = () => {
logit("🔤 Extension: ", extension);
if (!allowedFileTypes.includes(extension.toLowerCase())) {
toast({
title: "Invalid Image",
description:
"Please select an image with a valid extension like PNG, JPG, JPEG, or WEBP.",
title: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"),
description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DESC"),
});
resetImagePaths();
}
@ -460,8 +466,8 @@ const Home = () => {
) {
logit("👎 No valid files dropped");
toast({
title: "Invalid Image",
description: "Please drag and drop an image",
title: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"),
description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"),
});
return;
}
@ -475,8 +481,8 @@ const Home = () => {
) {
logit("🚫 Invalid file dropped");
toast({
title: "Invalid Image",
description: "Please drag and drop an image",
title: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"),
description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"),
});
} else {
logit("🖼 Setting image path: ", filePath);
@ -504,8 +510,8 @@ const Home = () => {
!allowedFileTypes.includes(extension.toLowerCase())
) {
toast({
title: "Invalid Image",
description: "Please drag and drop an image",
title: t("APP.ERRORS.INVALID_IMAGE_ERROR.TITLE"),
description: t("APP.ERRORS.INVALID_IMAGE_ERROR.DRAG_DESC"),
});
} else {
setImagePath(filePath);
@ -522,7 +528,7 @@ const Home = () => {
setUpscaledImagePath("");
setUpscaledBatchFolderPath("");
if (imagePath !== "" || batchFolderPath !== "") {
setProgress("Hold on...");
setProgress(t("APP.INFOS.IMAGE_PROCESSING.WAIT"));
// Double Upscayl
if (doubleUpscayl) {
window.electron.send<DoubleUpscaylPayload>(COMMAND.DOUBLE_UPSCAYL, {
@ -576,8 +582,8 @@ const Home = () => {
}
} else {
toast({
title: "No image selected",
description: "Please select an image to upscale",
title: t("APP.ERRORS.NO_IMAGE_ERROR.TITLE"),
description: t("APP.ERRORS.NO_IMAGE_ERROR.DESC"),
});
logit("🚫 No valid image selected");
}
@ -601,7 +607,7 @@ const Home = () => {
{!showSidebar && (
<div className="fixed right-2 top-2 z-50 flex items-center justify-center gap-2 rounded-[7px] bg-base-300 px-2 py-1 font-medium text-base-content ">
<Logo className="w-5" />
Upscayl
{t("APP.TITLE")}
</div>
)}
@ -648,7 +654,7 @@ const Home = () => {
setShowCloudModal(true);
}}
>
Introducing Upscayl Cloud
{t("APP.INTRO")}
</button>
)}
@ -772,7 +778,9 @@ const Home = () => {
upscaledBatchFolderPath.length === 0 &&
batchFolderPath.length > 0 && (
<p className="select-none text-base-content">
<span className="font-bold">Selected folder:</span>{" "}
<span className="font-bold">
{t("APP.INFOS.IMAGE_PROCESSING.BATCH.SELECT")}
</span>{" "}
{batchFolderPath}
</p>
)}
@ -780,13 +788,13 @@ const Home = () => {
{batchMode && upscaledBatchFolderPath.length > 0 && (
<div className="z-50 flex flex-col items-center">
<p className="select-none py-4 font-bold text-base-content">
All done!
{t("APP.INFOS.IMAGE_PROCESSING.BATCH.DONE")}
</p>
<button
className="bg-gradient-blue btn btn-primary rounded-btn p-3 font-medium text-white/90 transition-colors"
onClick={openFolderHandler}
>
Open Upscayled Folder
{t("APP.INFOS.IMAGE_PROCESSING.BATCH.OPEN_DONE_FOLDER")}
</button>
</div>
)}
@ -852,13 +860,13 @@ const Home = () => {
itemOne={
<>
<p className="absolute bottom-1 left-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
Original
{t("APP.INFOS.COMPARISION.SLIDER_ORIGINAL")}
</p>
<img
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */
src={"file:///" + sanitizedImagePath}
alt="Original"
alt={t("APP.INFOS.COMPARISION.SLIDER_ORIGINAL")}
onMouseMove={handleMouseMove}
style={{
objectFit: "contain",
@ -872,7 +880,7 @@ const Home = () => {
itemTwo={
<>
<p className="absolute bottom-1 right-1 rounded-md bg-black p-1 text-sm font-medium text-white opacity-30">
Upscayled
{t("APP.INFOS.COMPARISION.SLIDER_PROCESSED")}
</p>
<img
/* USE REGEX TO GET THE FILENAME AND ENCODE IT INTO PROPER FORM IN ORDER TO AVOID ERRORS DUE TO SPECIAL CHARACTERS */