mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-07-17 18:07:23 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cef522649a | |||
| f95816d70d | |||
| b15231d752 | |||
| af792745fc | |||
| 22f976442b | |||
| 809a95c729 | |||
| cea4ece698 | |||
| ac03b70f94 | |||
| 95f84248ab | |||
| dd5357d6c3 | |||
| 7b7849a54a |
@@ -1,6 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## v0.28.2 (2026-07-12)
|
||||
|
||||
### Features
|
||||
|
||||
- sha256 checksum for self-updates
|
||||
- progress bar for extraction
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- properly handle location spoofing for socks5 proxies
|
||||
|
||||
### Refactoring
|
||||
|
||||
- api cleanup
|
||||
|
||||
### Maintenance
|
||||
|
||||
- chore: version bump
|
||||
- chore: linting
|
||||
- ci(deps): bump the github-actions group with 2 updates
|
||||
- chore: update flake.nix for v0.28.1 [skip ci] (#493)
|
||||
|
||||
|
||||
## v0.28.1 (2026-07-09)
|
||||
|
||||
### Refactoring
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
| | Apple Silicon | Intel |
|
||||
|---|---|---|
|
||||
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_x64.dmg) |
|
||||
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_x64.dmg) |
|
||||
|
||||
Or install via Homebrew:
|
||||
|
||||
@@ -56,15 +56,15 @@ brew install --cask donut
|
||||
|
||||
### Windows
|
||||
|
||||
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_x64-portable.zip)
|
||||
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_x64-portable.zip)
|
||||
|
||||
### Linux
|
||||
|
||||
| Format | x86_64 | ARM64 |
|
||||
|---|---|---|
|
||||
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_arm64.deb) |
|
||||
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut-0.28.1-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut-0.28.1-1.aarch64.rpm) |
|
||||
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_aarch64.AppImage) |
|
||||
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_arm64.deb) |
|
||||
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut-0.28.2-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut-0.28.2-1.aarch64.rpm) |
|
||||
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_aarch64.AppImage) |
|
||||
<!-- install-links-end -->
|
||||
|
||||
Or install via package manager:
|
||||
@@ -135,6 +135,13 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
<sub><b>Hassiy</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/xenos1337">
|
||||
<img src="https://avatars.githubusercontent.com/u/66328734?v=4" width="100;" alt="xenos1337"/>
|
||||
<br />
|
||||
<sub><b>xenos</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/webees">
|
||||
<img src="https://avatars.githubusercontent.com/u/5155291?v=4" width="100;" alt="webees"/>
|
||||
@@ -156,6 +163,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
<sub><b>Huy Le</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/drunkod">
|
||||
<img src="https://avatars.githubusercontent.com/u/9677471?v=4" width="100;" alt="drunkod"/>
|
||||
@@ -163,8 +172,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
<sub><b>drunkod</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/JorySeverijnse">
|
||||
<img src="https://avatars.githubusercontent.com/u/117462355?v=4" width="100;" alt="JorySeverijnse"/>
|
||||
|
||||
@@ -96,17 +96,17 @@
|
||||
pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" (
|
||||
pkgConfigLibs ++ map lib.getDev pkgConfigLibs
|
||||
);
|
||||
releaseVersion = "0.28.1";
|
||||
releaseVersion = "0.28.2";
|
||||
releaseAppImage =
|
||||
if system == "x86_64-linux" then
|
||||
pkgs.fetchurl {
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_amd64.AppImage";
|
||||
hash = "sha256-ItjyK206mpwMXzTNnSHHM8L2R8EjvEP3mrKnz6ze4oI=";
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_amd64.AppImage";
|
||||
hash = "sha256-+CqHiPMg4oczNiPg+MC6jvp0CUcK4kb5yeyk+QDbAWY=";
|
||||
}
|
||||
else if system == "aarch64-linux" then
|
||||
pkgs.fetchurl {
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.28.1/Donut_0.28.1_aarch64.AppImage";
|
||||
hash = "sha256-ZdQKOJRwVB9Wb/ncC8j9ezoaOqPuj0zjuDB3N3r2JYY=";
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.28.2/Donut_0.28.2_aarch64.AppImage";
|
||||
hash = "sha256-HodokW2ySIpdpW7Hyqpwsm8whQ0hHldlSg11Sl1UW3k=";
|
||||
}
|
||||
else
|
||||
null;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "donutbrowser",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack -p 12341",
|
||||
|
||||
Generated
+1
-1
@@ -1797,7 +1797,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "donutbrowser"
|
||||
version = "0.28.1"
|
||||
version = "0.28.2"
|
||||
dependencies = [
|
||||
"aes 0.9.1",
|
||||
"aes-gcm 0.11.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "donutbrowser"
|
||||
version = "0.28.1"
|
||||
version = "0.28.2"
|
||||
description = "Simple Yet Powerful Anti-Detect Browser"
|
||||
authors = ["zhom@github"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Donut",
|
||||
"version": "0.28.1",
|
||||
"version": "0.28.2",
|
||||
"identifier": "com.donutbrowser",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm copy-proxy-binary && pnpm dev",
|
||||
|
||||
@@ -198,11 +198,11 @@ export function AccountPage({
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
|
||||
<DialogContent className="flex max-h-[calc(100vh-4rem)] max-w-2xl flex-col">
|
||||
<DialogContent className="flex max-h-[calc(100vh-5rem)] max-w-3xl flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
|
||||
subPage && "mx-auto w-full max-w-2xl",
|
||||
"min-h-0 flex-1 overflow-y-auto",
|
||||
subPage && "mx-auto w-full max-w-3xl",
|
||||
)}
|
||||
>
|
||||
<AnimatedTabs defaultValue="account">
|
||||
|
||||
@@ -1101,7 +1101,7 @@ export function ExtensionManagementDialog({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
|
||||
<DialogContent className="flex max-h-[90vh] max-w-[min(80rem,calc(100%-4rem))] flex-col">
|
||||
<DialogContent className="flex max-h-[85vh] max-w-[min(80rem,calc(100%-4rem))] flex-col">
|
||||
{!subPage && (
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
|
||||
@@ -557,7 +557,7 @@ export function GroupManagementDialog({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={onClose} subPage={subPage}>
|
||||
<DialogContent className="flex max-h-[90vh] max-w-[min(60rem,calc(100%-4rem))] flex-col">
|
||||
<DialogContent className="flex max-h-[85vh] max-w-[min(80rem,calc(100%-4rem))] flex-col">
|
||||
{!subPage && (
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("groups.management")}</DialogTitle>
|
||||
@@ -567,15 +567,13 @@ export function GroupManagementDialog({
|
||||
</DialogHeader>
|
||||
)}
|
||||
|
||||
<div className="flex min-h-0 w-full flex-1 flex-col gap-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="text-base font-semibold">
|
||||
{t("groups.pageTitle")}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("groups.pageDescription")}
|
||||
</p>
|
||||
<div className="@container flex min-h-0 w-full flex-1 flex-col">
|
||||
<div className="flex shrink-0 flex-wrap items-center justify-between gap-2">
|
||||
<div className="inline-flex h-7 items-center justify-center gap-1.5 rounded-md bg-accent px-3 text-sm font-medium whitespace-nowrap text-foreground">
|
||||
<span>{t("groups.pageTitle")}</span>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
{groups.length}
|
||||
</span>
|
||||
</div>
|
||||
<RippleButton
|
||||
size="sm"
|
||||
@@ -583,31 +581,34 @@ export function GroupManagementDialog({
|
||||
setCreateDialogOpen(true);
|
||||
}}
|
||||
className="flex shrink-0 items-center gap-2"
|
||||
aria-label={t("common.buttons.create")}
|
||||
>
|
||||
<GoPlus className="size-4" />
|
||||
{t("proxies.management.create")}
|
||||
<span className="hidden @2xl:inline">
|
||||
{t("common.buttons.create")}
|
||||
</span>
|
||||
</RippleButton>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<div className="mt-4 rounded-md bg-destructive/10 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Groups list */}
|
||||
{isLoading ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="mt-4 text-sm text-muted-foreground">
|
||||
{t("common.buttons.loading")}
|
||||
</div>
|
||||
) : groups.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="mt-4 text-sm text-muted-foreground">
|
||||
{t("groups.noGroupsDescription")}
|
||||
</div>
|
||||
) : (
|
||||
<FadingScrollArea
|
||||
className={cn(
|
||||
"min-h-0 flex-1",
|
||||
"mt-4 min-h-0 flex-1",
|
||||
selectedGroupsForBulk.length > 0 && "pb-16",
|
||||
)}
|
||||
style={
|
||||
|
||||
@@ -1665,6 +1665,30 @@ export function ProfilesDataTable({
|
||||
};
|
||||
}, [browserState.isClient, loadAllTags]);
|
||||
|
||||
// A running browser keeps the name it launched with, so close any inline
|
||||
// rename that was open when the profile entered a runtime transition.
|
||||
React.useEffect(() => {
|
||||
if (!profileToRename) return;
|
||||
|
||||
const profileId = profileToRename.id;
|
||||
const isRuntimeLocked =
|
||||
(browserState.isClient && runningProfiles.has(profileId)) ||
|
||||
launchingProfiles.has(profileId) ||
|
||||
stoppingProfiles.has(profileId);
|
||||
|
||||
if (isRuntimeLocked) {
|
||||
setProfileToRename(null);
|
||||
setNewProfileName("");
|
||||
setRenameError(null);
|
||||
}
|
||||
}, [
|
||||
profileToRename,
|
||||
runningProfiles,
|
||||
launchingProfiles,
|
||||
stoppingProfiles,
|
||||
browserState.isClient,
|
||||
]);
|
||||
|
||||
// Automatically deselect profiles that become running, updating, launching, or stopping
|
||||
React.useEffect(() => {
|
||||
const newSet = new Set(selectedProfiles);
|
||||
@@ -2479,7 +2503,15 @@ export function ProfilesDataTable({
|
||||
const profile = row.original as BrowserProfile;
|
||||
const rawName: string = row.getValue("name");
|
||||
const name = getBrowserDisplayName(rawName);
|
||||
const isEditing = meta.profileToRename?.id === profile.id;
|
||||
const isRuntimeLocked =
|
||||
(meta.isClient && meta.runningProfiles.has(profile.id)) ||
|
||||
meta.launchingProfiles.has(profile.id) ||
|
||||
meta.stoppingProfiles.has(profile.id);
|
||||
const isCrossOsBlocked = isCrossOsProfile(profile);
|
||||
const isEditing =
|
||||
meta.profileToRename?.id === profile.id &&
|
||||
!isRuntimeLocked &&
|
||||
!isCrossOsBlocked;
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
@@ -2530,45 +2562,44 @@ export function ProfilesDataTable({
|
||||
/>
|
||||
);
|
||||
|
||||
const isCrossOs = isCrossOsProfile(profile);
|
||||
const isCrossOsBlocked = isCrossOs;
|
||||
const isRunning =
|
||||
meta.isClient && meta.runningProfiles.has(profile.id);
|
||||
const isLaunching = meta.launchingProfiles.has(profile.id);
|
||||
const isStopping = meta.stoppingProfiles.has(profile.id);
|
||||
const isDisabled =
|
||||
isRunning || isLaunching || isStopping || isCrossOsBlocked;
|
||||
const lockedEmail = meta.getProfileLockEmail(profile.id);
|
||||
const isLocked = meta.isProfileLockedByAnother(profile.id);
|
||||
|
||||
return (
|
||||
<div className="flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"mr-auto h-6 max-w-full min-w-0 overflow-hidden rounded border-none bg-transparent px-2 py-1 text-left",
|
||||
isDisabled
|
||||
? "cursor-not-allowed opacity-60"
|
||||
: "cursor-pointer hover:bg-accent/50",
|
||||
)}
|
||||
onClick={() => {
|
||||
if (isDisabled) return;
|
||||
const nameControl = isRuntimeLocked ? (
|
||||
<div className="mr-auto h-6 max-w-full min-w-0 cursor-text overflow-hidden rounded px-2 py-1 text-left select-text">
|
||||
{display}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"mr-auto h-6 max-w-full min-w-0 overflow-hidden rounded border-none bg-transparent px-2 py-1 text-left",
|
||||
isCrossOsBlocked
|
||||
? "cursor-not-allowed opacity-60"
|
||||
: "cursor-pointer hover:bg-accent/50",
|
||||
)}
|
||||
onClick={() => {
|
||||
if (isCrossOsBlocked) return;
|
||||
meta.setProfileToRename(profile);
|
||||
meta.setNewProfileName(profile.name);
|
||||
meta.setRenameError(null);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isCrossOsBlocked) return;
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
meta.setProfileToRename(profile);
|
||||
meta.setNewProfileName(profile.name);
|
||||
meta.setRenameError(null);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isDisabled) return;
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
meta.setProfileToRename(profile);
|
||||
meta.setNewProfileName(profile.name);
|
||||
meta.setRenameError(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{display}
|
||||
</button>
|
||||
}
|
||||
}}
|
||||
>
|
||||
{display}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden">
|
||||
{nameControl}
|
||||
{isLocked && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -2595,14 +2626,7 @@ export function ProfilesDataTable({
|
||||
cell: ({ row, table }) => {
|
||||
const meta = table.options.meta as TableMeta;
|
||||
const profile = row.original;
|
||||
const isCrossOs = isCrossOsProfile(profile);
|
||||
const isCrossOsBlocked = isCrossOs;
|
||||
const isRunning =
|
||||
meta.isClient && meta.runningProfiles.has(profile.id);
|
||||
const isLaunching = meta.launchingProfiles.has(profile.id);
|
||||
const isStopping = meta.stoppingProfiles.has(profile.id);
|
||||
const isDisabled =
|
||||
isRunning || isLaunching || isStopping || isCrossOsBlocked;
|
||||
const isDisabled = isCrossOsProfile(profile);
|
||||
|
||||
return (
|
||||
<TagsCell
|
||||
@@ -2628,14 +2652,7 @@ export function ProfilesDataTable({
|
||||
cell: ({ row, table }) => {
|
||||
const meta = table.options.meta as TableMeta;
|
||||
const profile = row.original;
|
||||
const isCrossOs = isCrossOsProfile(profile);
|
||||
const isCrossOsBlocked = isCrossOs;
|
||||
const isRunning =
|
||||
meta.isClient && meta.runningProfiles.has(profile.id);
|
||||
const isLaunching = meta.launchingProfiles.has(profile.id);
|
||||
const isStopping = meta.stoppingProfiles.has(profile.id);
|
||||
const isDisabled =
|
||||
isRunning || isLaunching || isStopping || isCrossOsBlocked;
|
||||
const isDisabled = isCrossOsProfile(profile);
|
||||
|
||||
return (
|
||||
<NoteCell
|
||||
|
||||
@@ -78,7 +78,7 @@ function AnimatedTabsList({
|
||||
<TabsPrimitive.List
|
||||
data-slot="animated-tabs-list"
|
||||
className={cn(
|
||||
"relative inline-flex max-w-full scrollbar-none items-center gap-1 overflow-x-auto rounded-md p-0",
|
||||
"relative isolate inline-flex max-w-full scrollbar-none items-center gap-1 overflow-x-auto rounded-md p-0",
|
||||
className,
|
||||
)}
|
||||
onMouseLeave={(event) => {
|
||||
@@ -120,7 +120,7 @@ function AnimatedTabsTrigger({
|
||||
onMouseEnter?.(event);
|
||||
}}
|
||||
className={cn(
|
||||
"relative isolate inline-flex h-7 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors duration-150",
|
||||
"relative inline-flex h-7 cursor-pointer items-center justify-center gap-1.5 rounded-md px-3 text-sm font-medium whitespace-nowrap transition-colors duration-150",
|
||||
"text-muted-foreground hover:text-foreground",
|
||||
isActive && "text-foreground",
|
||||
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none",
|
||||
@@ -132,7 +132,7 @@ function AnimatedTabsTrigger({
|
||||
{showIndicator && (
|
||||
<motion.span
|
||||
layoutId={`animated-tabs-indicator-${indicatorId}`}
|
||||
className="absolute inset-0 -z-10 rounded-md bg-accent"
|
||||
className="pointer-events-none absolute inset-0 -z-10 rounded-md bg-accent"
|
||||
transition={{ type: "spring", stiffness: 360, damping: 32 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import ja from "./locales/ja.json";
|
||||
import ko from "./locales/ko.json";
|
||||
import pt from "./locales/pt.json";
|
||||
import ru from "./locales/ru.json";
|
||||
import tr from "./locales/tr.json";
|
||||
import vi from "./locales/vi.json";
|
||||
import zh from "./locales/zh.json";
|
||||
|
||||
@@ -20,6 +21,7 @@ export const SUPPORTED_LANGUAGES = [
|
||||
{ code: "ja", name: "Japanese", nativeName: "日本語" },
|
||||
{ code: "ko", name: "Korean", nativeName: "한국어" },
|
||||
{ code: "ru", name: "Russian", nativeName: "Русский" },
|
||||
{ code: "tr", name: "Turkish", nativeName: "Türkçe" },
|
||||
{ code: "vi", name: "Vietnamese", nativeName: "Tiếng Việt" },
|
||||
] as const;
|
||||
|
||||
@@ -68,6 +70,7 @@ const resources = {
|
||||
ja: { translation: ja },
|
||||
ko: { translation: ko },
|
||||
ru: { translation: ru },
|
||||
tr: { translation: tr },
|
||||
vi: { translation: vi },
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user