UPDATE: Add muti language

This commit is contained in:
2026-04-03 18:37:01 +07:00
parent 2b0c82bd7e
commit 45d345d7cb
24 changed files with 2451 additions and 430 deletions

View File

@@ -11,12 +11,16 @@
"@tailwindcss/vite": "^4.2.2", "@tailwindcss/vite": "^4.2.2",
"@tanstack/react-router": "^1.168.10", "@tanstack/react-router": "^1.168.10",
"@tanstack/react-router-devtools": "^1.166.11", "@tanstack/react-router-devtools": "^1.166.11",
"i18next": "^26.0.3",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.4",
"lucide-react": "^1.7.0", "lucide-react": "^1.7.0",
"motion": "^12.38.0", "motion": "^12.38.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-easy-crop": "^5.5.7", "react-easy-crop": "^5.5.7",
"react-i18next": "^17.0.2",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",
"zustand": "^5.0.12" "zustand": "^5.0.12"
@@ -274,6 +278,15 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.28.6", "version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -2518,6 +2531,15 @@
"integrity": "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==", "integrity": "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/cross-fetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.7.0"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3175,6 +3197,65 @@
"hermes-estree": "0.25.1" "hermes-estree": "0.25.1"
} }
}, },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"license": "MIT",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/i18next": {
"version": "26.0.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.3.tgz",
"integrity": "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg==",
"funding": [
{
"type": "individual",
"url": "https://www.locize.com/i18next"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
},
{
"type": "individual",
"url": "https://www.locize.com"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.29.2"
},
"peerDependencies": {
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
"integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-http-backend": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.4.tgz",
"integrity": "sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g==",
"license": "MIT",
"dependencies": {
"cross-fetch": "4.1.0"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "7.0.5", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -3763,6 +3844,26 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.37", "version": "2.0.37",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
@@ -4002,6 +4103,33 @@
"react-dom": ">=16.4.0" "react-dom": ">=16.4.0"
} }
}, },
"node_modules/react-i18next": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz",
"integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"html-parse-stringify": "^3.0.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"i18next": ">= 26.0.1",
"react": ">= 16.8.0",
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/react-toastify": { "node_modules/react-toastify": {
"version": "11.0.5", "version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
@@ -4305,6 +4433,12 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -4361,7 +4495,7 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"bin": { "bin": {
@@ -4454,7 +4588,6 @@
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
@@ -4549,6 +4682,21 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/webpack-virtual-modules": { "node_modules/webpack-virtual-modules": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
@@ -4556,6 +4704,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -14,12 +14,16 @@
"@tailwindcss/vite": "^4.2.2", "@tailwindcss/vite": "^4.2.2",
"@tanstack/react-router": "^1.168.10", "@tanstack/react-router": "^1.168.10",
"@tanstack/react-router-devtools": "^1.166.11", "@tanstack/react-router-devtools": "^1.166.11",
"i18next": "^26.0.3",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.4",
"lucide-react": "^1.7.0", "lucide-react": "^1.7.0",
"motion": "^12.38.0", "motion": "^12.38.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-easy-crop": "^5.5.7", "react-easy-crop": "^5.5.7",
"react-i18next": "^17.0.2",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",
"zustand": "^5.0.12" "zustand": "^5.0.12"

View File

@@ -0,0 +1,337 @@
{
"header": {
"home": "Home",
"tools": "Tools",
"language": "Language",
"diff": "Diff",
"client_update": "Client Update",
"plugins": "Plugins",
"analysis": "Analysis (Veritas)",
"firefly_tools": "Firefly Tools",
"how_to": "How to?",
"about": "About",
"settings": "Settings",
"minimize": "Minimize",
"close": "Close",
"by": "Firefly Shelter"
},
"background": {
"select_bg": "Select Background",
"choose_bg": "Choose Background",
"paste_url": "Paste image URL (https://...)",
"add": "Add",
"upload": "Upload",
"upload_from_computer": "Upload from computer",
"done": "Done",
"remove": "Remove",
"invalid_url": "Invalid url"
},
"close": {
"title": "Confirm Action",
"description": "Do you want to minimize the application to the system tray or close the application?",
"dont_ask": "Do not ask me again",
"minimize": "Minimize",
"close": "Close"
},
"setting": {
"title": "Settings",
"launcher_update_title": "Launcher Update",
"launcher_update_desc": "Check if your launcher is up to date.",
"launcher_update_btn": "Check for Launcher Updates",
"launcher_update_success": "Launcher is already up to date",
"closing_options_title": "Closing Options",
"set_dont_ask_again": "Set do not ask again",
"closing_auto_desc": "Next time you close the app, it will automatically {{action}} without asking.",
"action_minimize": "minimize to system tray",
"action_quit": "quit the app",
"version_label": "Version",
"server_label": "Server",
"proxy_label": "Proxy",
"launcher_label": "Launcher"
},
"home": {
"header_title": "Firefly GO",
"tooltip_how_to": "How to use all tools & commands",
"btn_select_game": "Select Game file",
"btn_selecting": "Selecting...",
"btn_start_game": "Start Game",
"btn_game_running": "Game is running",
"menu_change_path": "Change Game Path",
"menu_check_update": "Check for Updates Server & Proxy",
"menu_open_server": "Open server folder",
"menu_open_proxy": "Open proxy folder",
"menu_open_voice": "Open voice folder",
"status_updating_launcher": "Updating launcher",
"status_update_success": "Launcher updated successfully, auto closing after 5s",
"status_update_failed": "Launcher update failed, auto closing after 5s",
"status_wait": "Please wait...",
"status_complete": "Complete!",
"modal_update_title": "Update Data",
"modal_update_msg": "Do you want to update data server and proxy?",
"modal_download_title": "Download Data",
"modal_download_msg": "Data server and proxy download required",
"modal_self_update_title": "Update Launcher",
"modal_self_update_msg": "Do you want to update launcher?",
"btn_yes": "Yes",
"btn_no": "No",
"btn_download": "Download",
"error_game_dir": "Invalid game directory",
"error_file_type": "Incorrect file type",
"game_path_success": "Game path set successfully",
"no_updates": "No updates available",
"toast_pick_folder_error": "Pick folder error: ",
"toast_start_proxy_failed": "Failed to start proxy: ",
"toast_start_server_failed": "Failed to start server: ",
"toast_start_game_failed": "Failed to start game: ",
"toast_start_game_error": "Start game error: "
},
"diff": {
"header_title": "🎮 Game Update by Diff Tool",
"header_desc": "Help you update game with Diff Tool",
"game_dir_title": "Game Directory",
"btn_selecting": "Selecting...",
"btn_select_game": "Select Game Folder",
"game_dir_valid": "Valid game directory found!",
"game_dir_invalid": "Game directory not found. Please select the correct folder.",
"diff_file_title": "Diff file Directory",
"btn_select_diff": "Select Diff file Folder",
"diff_file_valid": "Valid diff file found!",
"diff_file_invalid": "Diff file not found. Please select the correct file.",
"btn_updating": "Updating...",
"btn_update_game": "Update Game",
"modal_update_title": "Update Game",
"status_wait": "Please wait...",
"inst_title": "📋 Instructions:",
"inst_step_1": "1. Click \"Select Game Folder\" and choose your game's root directory",
"inst_step_2": "2. Wait for the system to validate the game directory",
"inst_step_3": "3. Click \"Select Diff file Folder\" and choose your diff file's root directory",
"inst_step_4": "4. Wait for the system to validate the diff file directory",
"inst_step_5": "5. Click \"Update Game\" to save your changes",
"toast_game_dir_not_found": "Game directory not found. Please select the correct folder.",
"toast_no_folder_selected": "No folder path selected",
"toast_pick_folder_error": "Pick folder error: ",
"toast_invalid_file_type": "Not valid file type",
"toast_no_file_selected": "No file path selected",
"toast_pick_file_error": "Pick file error: ",
"toast_select_both": "Please select game directory and diff file",
"toast_update_completed": "Update game completed",
"stage_check_type": "Check Type Diff Tool",
"stage_version_validate": "Version Validate",
"stage_data_extract": "Data Extract",
"stage_cut_data": "Cut Data",
"stage_patch_data": "Patch Data",
"stage_delete_old_files": "Delete old files"
},
"language": {
"header_title": "🎮 Game Language Manager",
"header_desc": "Manage text and voice language settings for your game",
"game_dir_title": "Game Directory",
"btn_select_game": "Select Game Folder",
"btn_selecting": "Selecting...",
"game_dir_valid": "Valid game directory found!",
"game_dir_invalid": "Game directory not found. Please select the correct folder.",
"current_languages_title": "Current Languages",
"text_language": "Text Language",
"voice_language": "Voice Language",
"language_settings_title": "Language Settings",
"select_text_placeholder": "Select text language...",
"select_voice_placeholder": "Select voice language...",
"btn_apply": "Apply Language Settings",
"btn_applying": "Applying...",
"inst_title": "📋 Instructions:",
"inst_step_1": "1. Click \"Select Game Folder\" and choose your game's root directory",
"inst_step_2": "2. Wait for the system to validate the game directory",
"inst_step_3": "3. Select your preferred text and voice languages",
"inst_step_4": "4. Click \"Apply Language Settings\" to save your changes",
"toast_no_folder_selected": "No folder path selected",
"toast_pick_folder_error": "Pick Folder error: ",
"toast_set_language_success": "Language set successfully",
"toast_set_language_error": "Set language error: "
},
"howto": {
"title": "How to Use",
"sect1_title": "Using the Launcher Features",
"sect1_auto_update_pre": "Automatically update",
"sect1_auto_update_post": "and proxy tools when launching.",
"sect1_launch_game": "Launch the game directly through the launcher with correct parameters and runtime environment.",
"sect1_lang_pre": "Support switching in-game language (e.g., EN, JP, ZH, KR) via",
"sect1_lang_link": "Language Tools",
"sect1_patch_title": "Patch & Update Game Files",
"sect1_patch_desc_1_pre": "Use the",
"sect1_patch_desc_1_link": "Diff Tool",
"sect1_patch_desc_1_post": "(DiffPatch) for fast & lightweight incremental updates.",
"sect1_patch_desc_2_pre": "Supports",
"sect1_patch_desc_2_post": ", and custom diff formats.",
"sect2_title": "FireflyGo Chat Commands",
"sect2_desc_pre": "Below are in-game chat commands you can use. Some commands require you to enable",
"sect2_desc_bold": "Theorycraft Mode",
"sect2_desc_post": ".",
"sect2_tc_req_title": "Theorycraft Mode Required",
"sect2_tc_req_desc_pre": "The following commands are only available when",
"sect2_tc_req_desc_bold": "Theorycraft Mode",
"sect2_tc_req_desc_post": "is enabled:",
"sect2_extra_title": "Extra Settings",
"sect2_hidden_ui_title": "Hidden UI",
"sect2_hidden_ui_desc": "Instantly hides the entire game UI — often used in DIM showcase videos.",
"sect2_censor_title": "Disable Censorship",
"sect2_censor_desc": "Remove the Lens Flare censor effect 💀 for a cleaner experience.",
"sect2_tc_title": "Theorycraft Mode",
"sect2_tc_desc": "No need to type chat commands anymore — configure everything through the web: adjust monster HP, set cycles, view logs, and more.",
"sect2_cmd_title": "Available Commands:",
"sect2_cmd_tc": "Theorycraft Mode",
"sect2_cmd_tc_enable": "— Enable Theorycraft Mode",
"sect2_cmd_tc_disable": "— Disable Theorycraft Mode",
"sect2_cmd_cycle": "Cycle Control",
"sect2_cmd_tc_only": "(Theorycraft only)",
"sect2_cmd_cycle_desc": "— Set cycle count in battle",
"sect2_cmd_cycle_ex1_pre": "Example:",
"sect2_cmd_cycle_ex1_post": "sets battle to 30 cycles",
"sect2_cmd_cycle_ex2_post": "disables custom cycle",
"sect2_cmd_hp": "HP Override",
"sect2_cmd_hp_desc1": "— Set monster HP (only available in Theorycraft mode)",
"sect2_cmd_hp_desc2": "— Disable the set HP feature",
"sect2_cmd_hp_desc3": "— Set HP for each monster in a specific wave",
"sect2_cmd_hp_ex_pre": "Example:",
"sect2_cmd_hp_ex_post": "sets wave 1 monster1 HP=2,000,000 and monster2 HP=3,000,000",
"sect2_cmd_log": "Battle Log",
"sect2_cmd_log_desc1": "— Enable battle log output",
"sect2_cmd_log_desc2": "— Disable battle log",
"sect2_cmd_log_out_pre": "Output will be written as",
"sect2_cmd_skip": "Skip Nodes",
"sect2_cmd_skip_desc": "— Skip nodes in MOC / AS / Pure Fiction",
"sect2_cmd_skip_ex1_pre": "Example:",
"sect2_cmd_skip_ex1_post": "skips node 2",
"sect2_cmd_skip_ex2_post": "disables skipping",
"sect2_cmd_id": "Character Path Switch",
"sect2_cmd_id_desc": "— Switch path for multi-form characters",
"sect2_cmd_id_ex1_pre": "Example:",
"sect2_cmd_id_ex1_post": "to change MC (Trailblazer) form",
"sect2_cmd_id_ex2_pre": "Works with IDs like",
"sect2_cmd_update": "Refresh Data",
"sect2_cmd_update_desc_pre": "— Refresh server data from current",
"sect3_title": "Other Notes",
"sect3_admin_title": "Administrator Rights",
"sect3_admin_desc": "Always run the launcher as Administrator for file permission access.",
"sect3_backup_title": "Backup Data",
"sect3_backup_desc_pre": "Backup your",
"sect3_backup_desc_mid": "and",
"sect3_backup_desc_post": "regularly.",
"sect3_voice_title": "Enable Voice Packs in Beta Client",
"sect3_voice_step1_pre": "Copy the desired voice folder (e.g.,",
"sect3_voice_step1_mid": ") from:",
"sect3_voice_step1_post_pre": "to the beta folder by clicking",
"sect3_voice_step1_post_bold": "\"Open Voice Folder\"",
"sect3_voice_step1_post_post": "on the Home tab.",
"sect3_voice_step2": "When launching the game for the first time, it may delete the voice folder. If so, repeat step 1 to restore it.",
"btn_back": "Back to Home"
},
"about": {
"title": "About",
"p1_pre": "Hello! We are ",
"p1_post": ", a developer team passionate about building useful tools and improving user experiences.",
"p2_pre": "I created a lightweight and modern ",
"p2_highlight": "Game Launcher",
"p2_post": " to help users easily launch and manage their games with better performance and simplicity.",
"p3_pre": "The launcher is built using ",
"p3_mid": ", with a clean and responsive interface styled with ",
"p3_and": " and ",
"p3_post": ".",
"p4": "My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.",
"btn_back": "Back to Home"
},
"fireflytools": {
"title": "Firefly Tools",
"sect1_title": "About Firefly Tools",
"sect1_p1_pre": "This site is another version of ",
"sect1_p1_tool": "Firefly Tools ",
"sect1_p1_mid": "developed by ",
"sect1_p1_author": "Firefly Shelter",
"sect1_master_site": "Master Website",
"sect1_p2_pre": "The original tool was created by a third-party developer named ",
"sect1_p2_author": "Amazing",
"sect1_p2_post": ". This version is directly based on that work, without modification to core logic.",
"sect1_p3_pre": "There is also a more modern version by the same author available at ",
"sect2_title": "Main Features",
"sect2_feat1": "Configure characters, light cones, relics, traces, and eidolons easily in your browser.",
"sect2_feat2_pre": "Instantly apply setups to ",
"sect2_feat2_server": "Firefly GO Server",
"sect2_feat2_mid": " using ",
"sect2_feat2_conn": "Connect PS",
"sect2_feat2_post": " — no manual file uploads required.",
"sect2_feat3_title": "Extra Settings",
"sect2_feat3_desc_pre": "Enhance your ",
"sect2_feat3_desc_server": "Firefly GO Server",
"sect2_feat3_desc_post": " experience with extra features:",
"sect2_feat3_ui": "Hidden Game UI",
"sect2_feat3_ui_desc": "— remove the entire game interface.",
"sect2_feat3_censor": "Disable Censorship",
"sect2_feat3_censor_desc": "— get rid of Lens Flare censor 💀.",
"sect2_feat3_tc": "Theorycraft Mode",
"sect2_feat3_tc_desc": "— configure HP, cycles, and more via the web.",
"sect2_feat4_pre": "Export and import full builds using ",
"sect2_feat4_post": ".",
"sect2_feat5": "Fast testing workflow — no sync cooldowns, instant in-game updates.",
"sect3_title": "Getting Started",
"sect3_step1": "Access the tool through your browser at the self-hosted instance.",
"sect3_step2": "Configure your character builds with the intuitive web interface.",
"sect3_step3_pre": "Use ",
"sect3_step3_bold": "Connect PS",
"sect3_step3_post": " feature to instantly sync with your private server.",
"sect3_step4": "Test your builds in-game with real-time updates and modifications.",
"btn_back": "Back to Home"
},
"analysis": {
"title": "Firefly Analysis & Veritas Plugin",
"sect1_title": "About Veritas",
"sect1_p1_pre": "Veritas",
"sect1_p1_mid": " is a powerful ",
"sect1_p1_highlight": "Damage Logger",
"sect1_p1_post": " designed for analyzing damage in real-time during gameplay.",
"sect1_p2": "It's lightweight, fast, and easy to use for comprehensive damage analysis.",
"sect1_github": "GitHub Repository",
"sect2_title": "Web Analysis Tools",
"sect2_desc": "Use these web applications for real-time damage analysis with Veritas:",
"sect2_master": "Master Website",
"sect2_backup": "Backup Website",
"sect2_tip_pre": "Tip:",
"sect2_tip_post": " If your country has issues loading from the master site, please use the backup site instead.",
"sect3_title": "Installation Instructions",
"sect3_subtitle": "Important Setup Step",
"sect3_desc": "After downloading Veritas, you must rename the file for it to work properly:",
"sect3_rename": "Rename: ",
"sect3_place": "Then place ",
"sect3_place_post": " into your game directory.",
"sect4_title": "How to Use Web App",
"sect4_sub1": "For Firefly GO Local",
"sect4_sub1_step1_pre": "Launch the ",
"sect4_sub1_step1_game": "game",
"sect4_sub1_step1_mid": " and your ",
"sect4_sub1_step1_server": "Firefly GO Server (PS)",
"sect4_sub1_step1_post": ".",
"sect4_sub1_step2": "Open one of the web analysis tools.",
"sect4_sub1_step3_pre": "Go to ",
"sect4_sub1_step3_conn": "Connection Settings",
"sect4_sub1_step3_mid1": " → select ",
"sect4_sub1_step3_type": "Connection Type: PS",
"sect4_sub1_step3_mid2": " → click ",
"sect4_sub1_step3_btn": "Connect",
"sect4_sub1_step3_post": ".",
"sect4_sub1_step4": "Once connected, play the game. The tool will automatically analyze in the background.",
"sect4_sub2": "For Other Private Servers",
"sect4_sub2_step1_pre": "Launch the ",
"sect4_sub2_step1_game": "game",
"sect4_sub2_step1_mid": " and your ",
"sect4_sub2_step1_server": "Private Server",
"sect4_sub2_step1_post": ".",
"sect4_sub2_step2": "Open one of the web analysis tools.",
"sect4_sub2_step3_pre": "Go to ",
"sect4_sub2_step3_conn": "Connection Settings",
"sect4_sub2_step3_mid1": " → select ",
"sect4_sub2_step3_type": "Connection Type: Native",
"sect4_sub2_step3_mid2": " → click ",
"sect4_sub2_step3_btn": "Connect",
"sect4_sub2_step3_post": ".",
"sect4_sub2_step4": "Once connected, play the game normally.",
"btn_back": "Back to Home"
}
}

View File

@@ -0,0 +1,337 @@
{
"header": {
"home": "ホーム",
"tools": "ツール",
"language": "言語",
"diff": "差分",
"client_update": "クライアント更新",
"plugins": "プラグイン",
"analysis": "分析Veritas",
"firefly_tools": "Fireflyツール",
"how_to": "使い方",
"about": "概要",
"settings": "設定",
"minimize": "最小化",
"close": "閉じる",
"by": "Firefly Shelter"
},
"background": {
"select_bg": "背景を選択",
"choose_bg": "背景を選択",
"paste_url": "画像URLを貼り付け (https://...)",
"add": "追加",
"upload": "アップロード",
"upload_from_computer": "パソコンからアップロード",
"done": "完了",
"remove": "削除",
"invalid_url": "無効なURLです"
},
"close": {
"title": "実行の確認",
"description": "最小化してシステムトレイに格納しますか?それとも終了しますか?",
"dont_ask": "次回から表示しない",
"minimize": "最小化",
"close": "終了"
},
"setting": {
"title": "設定",
"launcher_update_title": "ランチャーの更新",
"launcher_update_desc": "最新バージョンかどうかを確認します。",
"launcher_update_btn": "更新を確認",
"launcher_update_success": "ランチャーは最新の状態です",
"closing_options_title": "終了オプション",
"set_dont_ask_again": "次回から表示しない",
"closing_auto_desc": "次回終了時、確認なしで自動的に{{action}}します。",
"action_minimize": "システムトレイに最小化",
"action_quit": "アプリを終了",
"version_label": "バージョン",
"server_label": "Server",
"proxy_label": "Proxy",
"launcher_label": "Launcher"
},
"home": {
"header_title": "Firefly GO",
"tooltip_how_to": "ツールとコマンドの使い方",
"btn_select_game": "ゲームファイルを選択",
"btn_selecting": "選択中...",
"btn_start_game": "ゲーム開始",
"btn_game_running": "実行中",
"menu_change_path": "ゲームパスを変更",
"menu_check_update": "サーバーとプロキシの更新を確認",
"menu_open_server": "サーバーフォルダを開く",
"menu_open_proxy": "プロキシフォルダを開く",
"menu_open_voice": "ボイスフォルダを開く",
"status_updating_launcher": "ランチャーを更新中",
"status_update_success": "更新完了。5秒後に閉じます",
"status_update_failed": "更新失敗。5秒後に閉じます",
"status_wait": "お待ちください...",
"status_complete": "完了!",
"modal_update_title": "データ更新",
"modal_update_msg": "サーバーとプロキシを更新しますか?",
"modal_download_title": "データダウンロード",
"modal_download_msg": "サーバーとプロキシのダウンロードが必要です",
"modal_self_update_title": "ランチャー更新",
"modal_self_update_msg": "ランチャーを更新しますか?",
"btn_yes": "はい",
"btn_no": "いいえ",
"btn_download": "ダウンロード",
"error_game_dir": "無効なゲームディレクトリ",
"error_file_type": "ファイル形式が正しくありません",
"game_path_success": "ゲームパスが設定されました",
"no_updates": "利用可能なアップデートはありません",
"toast_pick_folder_error": "フォルダ選択エラー: ",
"toast_start_proxy_failed": "プロキシの起動に失敗しました: ",
"toast_start_server_failed": "サーバーの起動に失敗しました: ",
"toast_start_game_failed": "ゲームの起動に失敗しました: ",
"toast_start_game_error": "ゲーム起動エラー: "
},
"diff": {
"header_title": "🎮 Diff Toolによるゲーム更新",
"header_desc": "Diff Toolを使用したゲームの更新をサポートします",
"game_dir_title": "ゲームディレクトリ",
"btn_selecting": "選択中...",
"btn_select_game": "ゲームフォルダを選択",
"game_dir_valid": "有効なゲームディレクトリが見つかりました!",
"game_dir_invalid": "ゲームディレクトリが見つかりません。正しいフォルダを選択してください。",
"diff_file_title": "Diffファイルディレクトリ",
"btn_select_diff": "Diffファイルを選択",
"diff_file_valid": "有効なDiffファイルが見つかりました",
"diff_file_invalid": "Diffファイルが見つかりません。正しいファイルを選択してください。",
"btn_updating": "更新中...",
"btn_update_game": "ゲームを更新",
"modal_update_title": "ゲームを更新",
"status_wait": "お待ちください...",
"inst_title": "📋 手順:",
"inst_step_1": "1. 「ゲームフォルダを選択」をクリックし、ゲームのルートディレクトリを選択します",
"inst_step_2": "2. システムがゲームディレクトリを検証するのを待ちます",
"inst_step_3": "3. 「Diffファイルを選択」をクリックし、Diffファイルを選択します",
"inst_step_4": "4. システムがDiffファイルを検証するのを待ちます",
"inst_step_5": "5. 「ゲームを更新」をクリックして変更を保存します",
"toast_game_dir_not_found": "ゲームディレクトリが見つかりません。正しいフォルダを選択してください。",
"toast_no_folder_selected": "フォルダパスが選択されていません",
"toast_pick_folder_error": "フォルダ選択エラー: ",
"toast_invalid_file_type": "無効なファイル形式です",
"toast_no_file_selected": "ファイルパスが選択されていません",
"toast_pick_file_error": "ファイル選択エラー: ",
"toast_select_both": "ゲームディレクトリとDiffファイルの両方を選択してください",
"toast_update_completed": "ゲームの更新が完了しました",
"stage_check_type": "Diff Toolタイプの確認",
"stage_version_validate": "バージョンの検証",
"stage_data_extract": "データの抽出",
"stage_cut_data": "データの切り取り",
"stage_patch_data": "データのパッチ適用",
"stage_delete_old_files": "古いファイルの削除"
},
"language": {
"header_title": "🎮 ゲーム言語マネージャー",
"header_desc": "ゲームのテキストと音声言語の設定を管理します",
"game_dir_title": "ゲームディレクトリ",
"btn_select_game": "ゲームフォルダを選択",
"btn_selecting": "選択中...",
"game_dir_valid": "有効なゲームディレクトリが見つかりました!",
"game_dir_invalid": "ゲームディレクトリが見つかりません。正しいフォルダを選択してください。",
"current_languages_title": "現在の言語",
"text_language": "テキスト言語",
"voice_language": "音声言語",
"language_settings_title": "言語設定",
"select_text_placeholder": "テキスト言語を選択...",
"select_voice_placeholder": "音声言語を選択...",
"btn_apply": "言語設定を適用",
"btn_applying": "適用中...",
"inst_title": "📋 手順:",
"inst_step_1": "1. 「ゲームフォルダを選択」をクリックし、ゲームのルートディレクトリを選択します",
"inst_step_2": "2. システムがゲームディレクトリを検証するのを待ちます",
"inst_step_3": "3. 希望するテキスト言語と音声言語を選択します",
"inst_step_4": "4. 「言語設定を適用」をクリックして変更を保存します",
"toast_no_folder_selected": "フォルダパスが選択されていません",
"toast_pick_folder_error": "フォルダ選択エラー: ",
"toast_set_language_success": "言語が正常に設定されました",
"toast_set_language_error": "言語設定エラー: "
},
"howto": {
"title": "使い方",
"sect1_title": "ランチャー機能の使用",
"sect1_auto_update_pre": "起動時に",
"sect1_auto_update_post": "およびプロキシツールを自動的に更新します。",
"sect1_launch_game": "正しいパラメータと実行環境で、ランチャーから直接ゲームを起動します。",
"sect1_lang_pre": "ゲーム内言語(例: EN, JP, ZH, KRの切り替えをサポートします",
"sect1_lang_link": "言語ツール",
"sect1_patch_title": "ゲームファイルのパッチと更新",
"sect1_patch_desc_1_pre": "高速で軽量な差分更新には",
"sect1_patch_desc_1_link": "Diff Tool",
"sect1_patch_desc_1_post": "DiffPatchを使用します。",
"sect1_patch_desc_2_pre": "サポート形式:",
"sect1_patch_desc_2_post": "、およびカスタムdiffフォーマット。",
"sect2_title": "FireflyGo チャットコマンド",
"sect2_desc_pre": "ゲーム内で使用できるチャットコマンドは以下の通りです。一部のコマンドは",
"sect2_desc_bold": "Theorycraftモード",
"sect2_desc_post": "を有効にする必要があります。",
"sect2_tc_req_title": "Theorycraftモードが必要",
"sect2_tc_req_desc_pre": "以下のコマンドは",
"sect2_tc_req_desc_bold": "Theorycraftモード",
"sect2_tc_req_desc_post": "が有効な場合のみ使用できます:",
"sect2_extra_title": "追加設定",
"sect2_hidden_ui_title": "UIを隠す",
"sect2_hidden_ui_desc": "ゲームUI全体を即座に非表示にします — DIMショーケースビデオでよく使用されます。",
"sect2_censor_title": "検閲の無効化",
"sect2_censor_desc": "よりクリーンな体験のためにレンズフレア検閲効果 💀 を削除します。",
"sect2_tc_title": "Theorycraftモード",
"sect2_tc_desc": "チャットコマンドを入力する必要はもうありません — ウェブからすべてを設定できますモンスターのHP調整、サイクルの設定、ログの表示など。",
"sect2_cmd_title": "利用可能なコマンド:",
"sect2_cmd_tc": "Theorycraftモード",
"sect2_cmd_tc_enable": "— Theorycraftモードを有効にする",
"sect2_cmd_tc_disable": "— Theorycraftモードを無効にする",
"sect2_cmd_cycle": "サイクル制御",
"sect2_cmd_tc_only": "(Theorycraftのみ)",
"sect2_cmd_cycle_desc": "— 戦闘でのサイクル数を設定する",
"sect2_cmd_cycle_ex1_pre": "例:",
"sect2_cmd_cycle_ex1_post": "は戦闘を30サイクルに設定します",
"sect2_cmd_cycle_ex2_post": "はカスタムサイクルを無効にします",
"sect2_cmd_hp": "HPの上書き",
"sect2_cmd_hp_desc1": "— モンスターのHPを設定する (Theorycraftモードでのみ利用可能)",
"sect2_cmd_hp_desc2": "— HP設定機能を無効にする",
"sect2_cmd_hp_desc3": "— 特定のウェーブの各モンスターにHPを設定する",
"sect2_cmd_hp_ex_pre": "例:",
"sect2_cmd_hp_ex_post": "はウェーブ1のモンスター1のHPを2,000,000に、モンスター2のHPを3,000,000に設定します",
"sect2_cmd_log": "バトルログ",
"sect2_cmd_log_desc1": "— バトルログ出力を有効にする",
"sect2_cmd_log_desc2": "— バトルログを無効にする",
"sect2_cmd_log_out_pre": "出力は以下の形式で書き込まれます:",
"sect2_cmd_skip": "ノードのスキップ",
"sect2_cmd_skip_desc": "— MOC / AS / 虚構叙事でノードをスキップする",
"sect2_cmd_skip_ex1_pre": "例:",
"sect2_cmd_skip_ex1_post": "はード2をスキップします",
"sect2_cmd_skip_ex2_post": "はスキップを無効にします",
"sect2_cmd_id": "キャラクター運命の切り替え",
"sect2_cmd_id_desc": "— 複数形態を持つキャラクターの運命を切り替える",
"sect2_cmd_id_ex1_pre": "例:",
"sect2_cmd_id_ex1_post": "は主人公(開拓者)の形態を変更します",
"sect2_cmd_id_ex2_pre": "対応ID例:",
"sect2_cmd_update": "データのリフレッシュ",
"sect2_cmd_update_desc_pre": "— 現在のファイルからサーバーデータを更新します:",
"sect3_title": "その他の注意事項",
"sect3_admin_title": "管理者権限",
"sect3_admin_desc": "ファイル権限にアクセスするため、常にランチャーを管理者として実行してください。",
"sect3_backup_title": "データのバックアップ",
"sect3_backup_desc_pre": "定期的に",
"sect3_backup_desc_mid": "と",
"sect3_backup_desc_post": "をバックアップしてください。",
"sect3_voice_title": "ベータクライアントでボイスパックを有効にする",
"sect3_voice_step1_pre": "希望するボイスフォルダ (例:",
"sect3_voice_step1_mid": ") を以下からコピーし:",
"sect3_voice_step1_post_pre": "ホームタブの",
"sect3_voice_step1_post_bold": "「ボイスフォルダを開く」",
"sect3_voice_step1_post_post": "をクリックしてベータフォルダに配置します。",
"sect3_voice_step2": "ゲームを初めて起動した際、ボイスフォルダが削除される場合があります。その場合は、手順1を繰り返して復元してください。",
"btn_back": "ホームに戻る"
},
"about": {
"title": "概要",
"p1_pre": "こんにちは!私たちは ",
"p1_post": " です。便利なツールの構築とユーザー体験の向上に情熱を注ぐ開発チームです。",
"p2_pre": "ユーザーがより良いパフォーマンスとシンプルさで簡単にゲームを起動・管理できるように、軽量でモダンな ",
"p2_highlight": "Game Launcher",
"p2_post": " を作成しました。",
"p3_pre": "ランチャーは ",
"p3_mid": " を使用して構築されており、",
"p3_and": " と ",
"p3_post": " でデザインされたクリーンでレスポンシブなインターフェースを備えています。",
"p4": "私の目標は、高速で効率的、そして使って楽しいツールを作ることです。このランチャーはその始まりに過ぎません。",
"btn_back": "ホームに戻る"
},
"fireflytools": {
"title": "Firefly ツール",
"sect1_title": "Firefly ツールについて",
"sect1_p1_pre": "このサイトは ",
"sect1_p1_tool": "Firefly Tools ",
"sect1_p1_mid": "の別バージョンであり、開発元は ",
"sect1_p1_author": "Firefly Shelter",
"sect1_master_site": "マスターウェブサイト",
"sect1_p2_pre": "オリジナルのツールは、",
"sect1_p2_author": "Amazing",
"sect1_p2_post": " というサードパーティの開発者によって作成されました。このバージョンは、コアロジックを変更することなく、その作業に直接基づいています。",
"sect1_p3_pre": "同じ作者によるよりモダンなバージョンも以下で利用可能です:",
"sect2_title": "主な機能",
"sect2_feat1": "ブラウザでキャラクター、光円錐、遺物、軌跡、星魂を簡単に設定できます。",
"sect2_feat2_pre": "",
"sect2_feat2_server": "Firefly GO Server",
"sect2_feat2_mid": " に ",
"sect2_feat2_conn": "Connect PS",
"sect2_feat2_post": " を使用して設定を即座に適用します — 手動でのファイルアップロードは不要です。",
"sect2_feat3_title": "追加設定",
"sect2_feat3_desc_pre": "追加機能で ",
"sect2_feat3_desc_server": "Firefly GO Server",
"sect2_feat3_desc_post": " の体験を向上させます:",
"sect2_feat3_ui": "UIを隠す",
"sect2_feat3_ui_desc": "— ゲームインターフェース全体を削除します。",
"sect2_feat3_censor": "検閲の無効化",
"sect2_feat3_censor_desc": "— レンズフレア検閲 💀 を取り除きます。",
"sect2_feat3_tc": "Theorycraft モード",
"sect2_feat3_tc_desc": "— ウェブ経由でHP、サイクルなどを設定します。",
"sect2_feat4_pre": "を使用して完全なビルドをエクスポートおよびインポートします:",
"sect2_feat4_post": "。",
"sect2_feat5": "高速なテストワークフロー — 同期のクールダウンなし、ゲーム内の即時更新。",
"sect3_title": "はじめに",
"sect3_step1": "セルフホストされたインスタンスのブラウザからツールにアクセスします。",
"sect3_step2": "直感的なウェブインターフェースでキャラクタービルドを設定します。",
"sect3_step3_pre": "",
"sect3_step3_bold": "Connect PS",
"sect3_step3_post": " 機能を使用して、プライベートサーバーと即座に同期します。",
"sect3_step4": "リアルタイムの更新と変更を使用して、ゲーム内でビルドをテストします。",
"btn_back": "ホームに戻る"
},
"analysis": {
"title": "Firefly 分析 & Veritas プラグイン",
"sect1_title": "Veritas について",
"sect1_p1_pre": "Veritas",
"sect1_p1_mid": " は、ゲームプレイ中のダメージをリアルタイムで分析するために設計された強力な ",
"sect1_p1_highlight": "ダメージロガー",
"sect1_p1_post": " です。",
"sect1_p2": "軽量で高速、包括的なダメージ分析に使いやすいツールです。",
"sect1_github": "GitHub リポジトリ",
"sect2_title": "Web 分析ツール",
"sect2_desc": "Veritas を使用したリアルタイムダメージ分析には、以下の Web アプリケーションを使用してください:",
"sect2_master": "マスターウェブサイト",
"sect2_backup": "バックアップウェブサイト",
"sect2_tip_pre": "ヒント:",
"sect2_tip_post": " マスターサイトの読み込みに問題がある場合は、バックアップサイトをご利用ください。",
"sect3_title": "インストール手順",
"sect3_subtitle": "重要なセットアップ手順",
"sect3_desc": "Veritas をダウンロードした後、正常に動作させるにはファイル名を変更する必要があります:",
"sect3_rename": "名前の変更: ",
"sect3_place": "その後、",
"sect3_place_post": " をゲームディレクトリに配置します。",
"sect4_title": "Web アプリの使い方",
"sect4_sub1": "Firefly GO ローカル用",
"sect4_sub1_step1_pre": "",
"sect4_sub1_step1_game": "ゲーム",
"sect4_sub1_step1_mid": " と ",
"sect4_sub1_step1_server": "Firefly GO サーバー (PS)",
"sect4_sub1_step1_post": " を起動します。",
"sect4_sub1_step2": "いずれかの Web 分析ツールを開きます。",
"sect4_sub1_step3_pre": "",
"sect4_sub1_step3_conn": "Connection Settings",
"sect4_sub1_step3_mid1": " に移動し、",
"sect4_sub1_step3_type": "Connection Type: PS",
"sect4_sub1_step3_mid2": " を選択して、",
"sect4_sub1_step3_btn": "Connect",
"sect4_sub1_step3_post": " をクリックします。",
"sect4_sub1_step4": "接続後、ゲームをプレイしてください。ツールがバックグラウンドで自動的に分析を行います。",
"sect4_sub2": "その他のプライベートサーバー用",
"sect4_sub2_step1_pre": "",
"sect4_sub2_step1_game": "ゲーム",
"sect4_sub2_step1_mid": " と ",
"sect4_sub2_step1_server": "プライベートサーバー",
"sect4_sub2_step1_post": " を起動します。",
"sect4_sub2_step2": "いずれかの Web 分析ツールを開きます。",
"sect4_sub2_step3_pre": "",
"sect4_sub2_step3_conn": "Connection Settings",
"sect4_sub2_step3_mid1": " に移動し、",
"sect4_sub2_step3_type": "Connection Type: Native",
"sect4_sub2_step3_mid2": " を選択して、",
"sect4_sub2_step3_btn": "Connect",
"sect4_sub2_step3_post": " をクリックします。",
"sect4_sub2_step4": "接続後、通常通りゲームをプレイしてください。",
"btn_back": "ホームに戻る"
}
}

View File

@@ -0,0 +1,337 @@
{
"header": {
"home": "홈",
"tools": "도구",
"language": "언어",
"diff": "비교",
"client_update": "클라이언트 업데이트",
"plugins": "플러그인",
"analysis": "분석 (Veritas)",
"firefly_tools": "Firefly 도구",
"how_to": "사용 방법",
"about": "정보",
"settings": "설정",
"minimize": "최소화",
"close": "닫기",
"by": "Firefly Shelter"
},
"background": {
"select_bg": "배경 선택",
"choose_bg": "배경 선택",
"paste_url": "이미지 URL 붙여넣기 (https://...)",
"add": "추가",
"upload": "업로드",
"upload_from_computer": "컴퓨터에서 업로드",
"done": "완료",
"remove": "삭제",
"invalid_url": "잘못된 URL입니다"
},
"close": {
"title": "작업 확인",
"description": "앱을 트레이로 최소화하시겠습니까, 아니면 종료하시겠습니까?",
"dont_ask": "다시 묻지 않음",
"minimize": "최소화",
"close": "종료"
},
"setting": {
"title": "설정",
"launcher_update_title": "런처 업데이트",
"launcher_update_desc": "런처가 최신 상태인지 확인합니다.",
"launcher_update_btn": "업데이트 확인",
"launcher_update_success": "런처가 이미 최신 버전입니다",
"closing_options_title": "종료 옵션",
"set_dont_ask_again": "다시 묻지 않음 설정",
"closing_auto_desc": "다음 종료 시 묻지 않고 자동으로 {{action}}합니다.",
"action_minimize": "트레이로 최소화",
"action_quit": "앱 종료",
"version_label": "버전",
"server_label": "Server",
"proxy_label": "Proxy",
"launcher_label": "Launcher"
},
"home": {
"header_title": "Firefly GO",
"tooltip_how_to": "도구 및 명령 사용법",
"btn_select_game": "게임 파일 선택",
"btn_selecting": "선택 중...",
"btn_start_game": "게임 시작",
"btn_game_running": "게임 실행 중",
"menu_change_path": "게임 경로 변경",
"menu_check_update": "서버 및 프록시 업데이트 확인",
"menu_open_server": "서버 폴더 열기",
"menu_open_proxy": "프록시 폴더 열기",
"menu_open_voice": "음성 폴더 열기",
"status_updating_launcher": "런처 업데이트 중",
"status_update_success": "업데이트 완료, 5초 후 종료",
"status_update_failed": "업데이트 실패, 5초 후 종료",
"status_wait": "잠시만 기다려 주세요...",
"status_complete": "완료!",
"modal_update_title": "데이터 업데이트",
"modal_update_msg": "서버 및 프록시 데이터를 업데이트하시겠습니까?",
"modal_download_title": "데이터 다운로드",
"modal_download_msg": "서버 및 프록시 데이터 다운로드가 필요합니다",
"modal_self_update_title": "런처 업데이트",
"modal_self_update_msg": "런처를 업데이트하시겠습니까?",
"btn_yes": "예",
"btn_no": "아니요",
"btn_download": "다운로드",
"error_game_dir": "잘못된 게임 디렉토리",
"error_file_type": "잘못된 파일 형식",
"game_path_success": "게임 경로 설정 완료",
"no_updates": "업데이트가 없습니다",
"toast_pick_folder_error": "폴더 선택 오류: ",
"toast_start_proxy_failed": "프록시 시작 실패: ",
"toast_start_server_failed": "서버 시작 실패: ",
"toast_start_game_failed": "게임 시작 실패: ",
"toast_start_game_error": "게임 시작 오류: "
},
"diff": {
"header_title": "🎮 Diff Tool로 게임 업데이트",
"header_desc": "Diff Tool을 사용하여 게임을 업데이트하도록 도와줍니다",
"game_dir_title": "게임 디렉토리",
"btn_selecting": "선택 중...",
"btn_select_game": "게임 폴더 선택",
"game_dir_valid": "유효한 게임 디렉토리를 찾았습니다!",
"game_dir_invalid": "게임 디렉토리를 찾을 수 없습니다. 올바른 폴더를 선택해주세요.",
"diff_file_title": "Diff 파일 디렉토리",
"btn_select_diff": "Diff 파일 선택",
"diff_file_valid": "유효한 Diff 파일을 찾았습니다!",
"diff_file_invalid": "Diff 파일을 찾을 수 없습니다. 올바른 파일을 선택해주세요.",
"btn_updating": "업데이트 중...",
"btn_update_game": "게임 업데이트",
"modal_update_title": "게임 업데이트",
"status_wait": "잠시만 기다려 주세요...",
"inst_title": "📋 지침:",
"inst_step_1": "1. \"게임 폴더 선택\"을 클릭하고 게임의 루트 디렉토리를 선택합니다",
"inst_step_2": "2. 시스템이 게임 디렉토리를 검증할 때까지 기다립니다",
"inst_step_3": "3. \"Diff 파일 선택\"을 클릭하고 Diff 파일을 선택합니다",
"inst_step_4": "4. 시스템이 Diff 파일을 검증할 때까지 기다립니다",
"inst_step_5": "5. \"게임 업데이트\"를 클릭하여 변경 사항을 저장합니다",
"toast_game_dir_not_found": "게임 디렉토리를 찾을 수 없습니다. 올바른 폴더를 선택해주세요.",
"toast_no_folder_selected": "폴더 경로가 선택되지 않았습니다",
"toast_pick_folder_error": "폴더 선택 오류: ",
"toast_invalid_file_type": "유효하지 않은 파일 형식입니다",
"toast_no_file_selected": "파일 경로가 선택되지 않았습니다",
"toast_pick_file_error": "파일 선택 오류: ",
"toast_select_both": "게임 디렉토리와 Diff 파일을 모두 선택해주세요",
"toast_update_completed": "게임 업데이트가 완료되었습니다",
"stage_check_type": "Diff Tool 유형 확인",
"stage_version_validate": "버전 유효성 검사",
"stage_data_extract": "데이터 추출",
"stage_cut_data": "데이터 자르기",
"stage_patch_data": "데이터 패치",
"stage_delete_old_files": "오래된 파일 삭제"
},
"language": {
"header_title": "🎮 게임 언어 관리자",
"header_desc": "게임의 텍스트 및 음성 언어 설정을 관리합니다",
"game_dir_title": "게임 디렉토리",
"btn_select_game": "게임 폴더 선택",
"btn_selecting": "선택 중...",
"game_dir_valid": "유효한 게임 디렉토리를 찾았습니다!",
"game_dir_invalid": "게임 디렉토리를 찾을 수 없습니다. 올바른 폴더를 선택해주세요.",
"current_languages_title": "현재 언어",
"text_language": "텍스트 언어",
"voice_language": "음성 언어",
"language_settings_title": "언어 설정",
"select_text_placeholder": "텍스트 언어 선택...",
"select_voice_placeholder": "음성 언어 선택...",
"btn_apply": "언어 설정 적용",
"btn_applying": "적용 중...",
"inst_title": "📋 지침:",
"inst_step_1": "1. \"게임 폴더 선택\"을 클릭하고 게임의 루트 디렉토리를 선택합니다",
"inst_step_2": "2. 시스템이 게임 디렉토리를 검증할 때까지 기다립니다",
"inst_step_3": "3. 선호하는 텍스트 및 음성 언어를 선택합니다",
"inst_step_4": "4. \"언어 설정 적용\"을 클릭하여 변경 사항을 저장합니다",
"toast_no_folder_selected": "폴더 경로가 선택되지 않았습니다",
"toast_pick_folder_error": "폴더 선택 오류: ",
"toast_set_language_success": "언어 설정이 완료되었습니다",
"toast_set_language_error": "언어 설정 오류: "
},
"howto": {
"title": "사용 방법",
"sect1_title": "런처 기능 사용",
"sect1_auto_update_pre": "실행 시",
"sect1_auto_update_post": "및 프록시 도구를 자동으로 업데이트합니다.",
"sect1_launch_game": "올바른 매개변수와 실행 환경으로 런처를 통해 게임을 직접 실행합니다.",
"sect1_lang_pre": "게임 내 언어(예: EN, JP, ZH, KR) 전환 지원:",
"sect1_lang_link": "언어 도구",
"sect1_patch_title": "게임 파일 패치 및 업데이트",
"sect1_patch_desc_1_pre": "빠르고 가벼운 증분 업데이트를 위해",
"sect1_patch_desc_1_link": "Diff Tool",
"sect1_patch_desc_1_post": "(DiffPatch)을 사용합니다.",
"sect1_patch_desc_2_pre": "지원 형식:",
"sect1_patch_desc_2_post": "및 사용자 정의 diff 형식.",
"sect2_title": "FireflyGo 채팅 명령",
"sect2_desc_pre": "게임 내에서 사용할 수 있는 채팅 명령어입니다. 일부 명령은",
"sect2_desc_bold": "Theorycraft 모드",
"sect2_desc_post": "를 활성화해야 합니다.",
"sect2_tc_req_title": "Theorycraft 모드 필요",
"sect2_tc_req_desc_pre": "다음 명령은",
"sect2_tc_req_desc_bold": "Theorycraft 모드",
"sect2_tc_req_desc_post": "가 활성화된 경우에만 사용할 수 있습니다:",
"sect2_extra_title": "추가 설정",
"sect2_hidden_ui_title": "UI 숨기기",
"sect2_hidden_ui_desc": "DIM 쇼케이스 비디오에서 자주 사용되는 게임 UI 전체를 즉시 숨깁니다.",
"sect2_censor_title": "검열 비활성화",
"sect2_censor_desc": "더 깔끔한 경험을 위해 렌즈 플레어 검열 효과 💀를 제거합니다.",
"sect2_tc_title": "Theorycraft 모드",
"sect2_tc_desc": "더 이상 채팅 명령어를 입력할 필요가 없습니다 — 몬스터 HP 조정, 주기 설정, 로그 보기 등 모든 것을 웹을 통해 구성하세요.",
"sect2_cmd_title": "사용 가능한 명령:",
"sect2_cmd_tc": "Theorycraft 모드",
"sect2_cmd_tc_enable": "— Theorycraft 모드 활성화",
"sect2_cmd_tc_disable": "— Theorycraft 모드 비활성화",
"sect2_cmd_cycle": "주기 제어",
"sect2_cmd_tc_only": "(Theorycraft 전용)",
"sect2_cmd_cycle_desc": "— 전투에서 주기 수 설정",
"sect2_cmd_cycle_ex1_pre": "예:",
"sect2_cmd_cycle_ex1_post": "는 전투를 30주기로 설정합니다",
"sect2_cmd_cycle_ex2_post": "는 사용자 정의 주기를 비활성화합니다",
"sect2_cmd_hp": "HP 덮어쓰기",
"sect2_cmd_hp_desc1": "— 몬스터 HP 설정 (Theorycraft 모드에서만 사용 가능)",
"sect2_cmd_hp_desc2": "— HP 설정 기능 비활성화",
"sect2_cmd_hp_desc3": "— 특정 웨이브의 각 몬스터에 HP 설정",
"sect2_cmd_hp_ex_pre": "예:",
"sect2_cmd_hp_ex_post": "는 웨이브 1 몬스터1 HP를 2,000,000으로, 몬스터2 HP를 3,000,000으로 설정합니다",
"sect2_cmd_log": "전투 로그",
"sect2_cmd_log_desc1": "— 전투 로그 출력 활성화",
"sect2_cmd_log_desc2": "— 전투 로그 비활성화",
"sect2_cmd_log_out_pre": "출력은 다음 형식으로 작성됩니다:",
"sect2_cmd_skip": "노드 건너뛰기",
"sect2_cmd_skip_desc": "— 혼돈의 기억 / 허구 이야기에서 노드 건너뛰기",
"sect2_cmd_skip_ex1_pre": "예:",
"sect2_cmd_skip_ex1_post": "는 노드 2를 건너뜁니다",
"sect2_cmd_skip_ex2_post": "는 건너뛰기를 비활성화합니다",
"sect2_cmd_id": "캐릭터 운명의 길 전환",
"sect2_cmd_id_desc": "— 다중 형태를 가진 캐릭터의 운명의 길 전환",
"sect2_cmd_id_ex1_pre": "예:",
"sect2_cmd_id_ex1_post": "는 개척자 형태를 변경합니다",
"sect2_cmd_id_ex2_pre": "지원되는 ID 예:",
"sect2_cmd_update": "데이터 새로고침",
"sect2_cmd_update_desc_pre": "— 다음 파일에서 서버 데이터를 새로고침합니다:",
"sect3_title": "기타 참고 사항",
"sect3_admin_title": "관리자 권한",
"sect3_admin_desc": "파일 권한 액세스를 위해 항상 런처를 관리자 권한으로 실행하세요.",
"sect3_backup_title": "데이터 백업",
"sect3_backup_desc_pre": "정기적으로",
"sect3_backup_desc_mid": "및",
"sect3_backup_desc_post": "를 백업하세요.",
"sect3_voice_title": "베타 클라이언트에서 음성 팩 활성화",
"sect3_voice_step1_pre": "원하는 음성 폴더(예:",
"sect3_voice_step1_mid": ")를 다음 경로에서 복사하여:",
"sect3_voice_step1_post_pre": "홈 탭에서",
"sect3_voice_step1_post_bold": "\"음성 폴더 열기\"",
"sect3_voice_step1_post_post": "를 클릭해 베타 폴더에 넣습니다.",
"sect3_voice_step2": "게임을 처음 실행할 때 음성 폴더가 삭제될 수 있습니다. 그런 경우 1단계를 반복하여 복구하세요.",
"btn_back": "홈으로 돌아가기"
},
"about": {
"title": "소개",
"p1_pre": "안녕하세요! 저희는 유용한 도구를 개발하고 사용자 경험을 개선하는 데 열정을 쏟는 개발팀 ",
"p1_post": " 입니다.",
"p2_pre": "사용자가 더 나은 성능과 단순함으로 게임을 쉽게 실행하고 관리할 수 있도록 가볍고 현대적인 ",
"p2_highlight": "Game Launcher",
"p2_post": " 를 만들었습니다.",
"p3_pre": "런처는 ",
"p3_mid": " 을(를) 사용하여 구축되었으며, ",
"p3_and": " 및 ",
"p3_post": " (으)로 디자인된 깔끔하고 반응형인 인터페이스를 특징으로 합니다.",
"p4": "제 목표는 빠르고 효율적이며 사용하기 즐거운 도구를 만드는 것입니다 — 이 런처는 단지 시작일 뿐입니다.",
"btn_back": "홈으로 돌아가기"
},
"fireflytools": {
"title": "Firefly 도구",
"sect1_title": "Firefly 도구 소개",
"sect1_p1_pre": "이 사이트는 ",
"sect1_p1_tool": "Firefly Shelter",
"sect1_p1_mid": "에서 개발한 ",
"sect1_p1_author": "Firefly Tools",
"sect1_master_site": "마스터 웹사이트",
"sect1_p2_pre": "원본 도구는 ",
"sect1_p2_author": "Amazing",
"sect1_p2_post": " 이라는 타사 개발자가 만들었습니다. 이 버전은 핵심 논리를 수정하지 않고 해당 작업을 직접 기반으로 합니다.",
"sect1_p3_pre": "동일한 작성자의 더 현대적인 버전도 다음에서 사용할 수 있습니다: ",
"sect2_title": "주요 기능",
"sect2_feat1": "브라우저에서 캐릭터, 광추, 유물, 행적 및 성혼을 쉽게 구성하세요.",
"sect2_feat2_pre": "",
"sect2_feat2_server": "Firefly GO Server",
"sect2_feat2_mid": "에 ",
"sect2_feat2_conn": "Connect PS",
"sect2_feat2_post": "를 사용하여 설정을 즉시 적용합니다 — 수동 파일 업로드가 필요하지 않습니다.",
"sect2_feat3_title": "추가 설정",
"sect2_feat3_desc_pre": "추가 기능으로 ",
"sect2_feat3_desc_server": "Firefly GO Server",
"sect2_feat3_desc_post": " 환경을 향상시키세요:",
"sect2_feat3_ui": "게임 UI 숨기기",
"sect2_feat3_ui_desc": "— 전체 게임 인터페이스를 제거합니다.",
"sect2_feat3_censor": "검열 비활성화",
"sect2_feat3_censor_desc": "— 렌즈 플레어 검열 💀을 제거합니다.",
"sect2_feat3_tc": "Theorycraft 모드",
"sect2_feat3_tc_desc": "— 웹을 통해 HP, 주기 등을 구성합니다.",
"sect2_feat4_pre": "다음을 사용하여 전체 빌드를 내보내고 가져옵니다: ",
"sect2_feat4_post": ".",
"sect2_feat5": "빠른 테스트 워크플로 — 동기화 재사용 대기시간 없음, 즉각적인 게임 내 업데이트.",
"sect3_title": "시작하기",
"sect3_step1": "자체 호스팅 인스턴스에서 브라우저를 통해 도구에 액세스합니다.",
"sect3_step2": "직관적인 웹 인터페이스로 캐릭터 빌드를 구성합니다.",
"sect3_step3_pre": "",
"sect3_step3_bold": "Connect PS",
"sect3_step3_post": " 기능을 사용하여 사설 서버와 즉시 동기화합니다.",
"sect3_step4": "실시간 업데이트 및 수정 사항을 통해 게임 내에서 빌드를 테스트합니다.",
"btn_back": "홈으로 돌아가기"
},
"analysis": {
"title": "Firefly 분석 & Veritas 플러그인",
"sect1_title": "Veritas 소개",
"sect1_p1_pre": "Veritas",
"sect1_p1_mid": "는 게임 플레이 중 실시간으로 피해량을 분석하도록 설계된 강력한 ",
"sect1_p1_highlight": "데미지 로거",
"sect1_p1_post": "입니다.",
"sect1_p2": "포괄적인 피해 분석을 위해 가볍고 빠르며 사용하기 쉽습니다.",
"sect1_github": "GitHub 저장소",
"sect2_title": "웹 분석 도구",
"sect2_desc": "Veritas를 통한 실시간 피해 분석을 위해 다음 웹 애플리케이션을 사용하세요:",
"sect2_master": "마스터 웹사이트",
"sect2_backup": "백업 웹사이트",
"sect2_tip_pre": "팁:",
"sect2_tip_post": " 해당 국가에서 마스터 사이트 로딩에 문제가 있는 경우 백업 사이트를 대신 사용하세요.",
"sect3_title": "설치 지침",
"sect3_subtitle": "중요 설정 단계",
"sect3_desc": "Veritas를 다운로드한 후 정상적으로 작동하려면 파일 이름을 변경해야 합니다:",
"sect3_rename": "이름 변경: ",
"sect3_place": "그런 다음 ",
"sect3_place_post": "를 게임 디렉토리에 배치합니다.",
"sect4_title": "웹 앱 사용 방법",
"sect4_sub1": "Firefly GO 로컬용",
"sect4_sub1_step1_pre": "",
"sect4_sub1_step1_game": "게임",
"sect4_sub1_step1_mid": "과 ",
"sect4_sub1_step1_server": "Firefly GO 서버(PS)",
"sect4_sub1_step1_post": "를 실행합니다.",
"sect4_sub1_step2": "웹 분석 도구 중 하나를 엽니다.",
"sect4_sub1_step3_pre": "",
"sect4_sub1_step3_conn": "Connection Settings",
"sect4_sub1_step3_mid1": "로 이동 → ",
"sect4_sub1_step3_type": "Connection Type: PS",
"sect4_sub1_step3_mid2": " 선택 → ",
"sect4_sub1_step3_btn": "Connect",
"sect4_sub1_step3_post": "를 클릭합니다.",
"sect4_sub1_step4": "연결되면 게임을 플레이합니다. 도구가 백그라운드에서 자동으로 분석합니다.",
"sect4_sub2": "기타 사설 서버용",
"sect4_sub2_step1_pre": "",
"sect4_sub2_step1_game": "게임",
"sect4_sub2_step1_mid": "과 ",
"sect4_sub2_step1_server": "사설 서버",
"sect4_sub2_step1_post": "를 실행합니다.",
"sect4_sub2_step2": "웹 분석 도구 중 하나를 엽니다.",
"sect4_sub2_step3_pre": "",
"sect4_sub2_step3_conn": "Connection Settings",
"sect4_sub2_step3_mid1": "로 이동 → ",
"sect4_sub2_step3_type": "Connection Type: Native",
"sect4_sub2_step3_mid2": " 선택 → ",
"sect4_sub2_step3_btn": "Connect",
"sect4_sub2_step3_post": "를 클릭합니다.",
"sect4_sub2_step4": "연결되면 평소처럼 게임을 플레이합니다.",
"btn_back": "홈으로 돌아가기"
}
}

View File

@@ -0,0 +1,337 @@
{
"header": {
"home": "Trang chủ",
"tools": "Công cụ",
"language": "Ngôn ngữ",
"diff": "So sánh",
"client_update": "Cập nhật client",
"plugins": "Plugin",
"analysis": "Phân tích (Veritas)",
"firefly_tools": "Firefly Tools",
"how_to": "Hướng dẫn",
"about": "Giới thiệu",
"settings": "Cài đặt",
"minimize": "Thu nhỏ",
"close": "Đóng",
"by": "Firefly Shelter"
},
"background": {
"select_bg": "Chọn nền",
"choose_bg": "Chọn ảnh nền",
"paste_url": "Dán URL ảnh (https://...)",
"add": "Thêm",
"upload": "Tải lên",
"upload_from_computer": "Tải từ máy tính",
"done": "Xong",
"remove": "Xóa",
"invalid_url": "URL không hợp lệ"
},
"close": {
"title": "Xác nhận hành động",
"description": "Bạn muốn thu nhỏ ứng dụng xuống khay hệ thống hay đóng ứng dụng?",
"dont_ask": "Không hỏi lại tôi",
"minimize": "Thu nhỏ",
"close": "Đóng"
},
"setting": {
"title": "Cài đặt",
"launcher_update_title": "Cập nhật Launcher",
"launcher_update_desc": "Kiểm tra xem launcher của bạn đã là phiên bản mới nhất chưa.",
"launcher_update_btn": "Kiểm tra cập nhật",
"launcher_update_success": "Launcher đã là phiên bản mới nhất",
"closing_options_title": "Tùy chọn đóng",
"set_dont_ask_again": "Không hỏi lại lần sau",
"closing_auto_desc": "Lần tới khi bạn đóng, ứng dụng sẽ tự động {{action}} mà không cần hỏi.",
"action_minimize": "thu nhỏ xuống khay hệ thống",
"action_quit": "thoát ứng dụng",
"version_label": "Phiên bản",
"server_label": "Server",
"proxy_label": "Proxy",
"launcher_label": "Launcher"
},
"home": {
"header_title": "Firefly GO",
"tooltip_how_to": "Cách sử dụng công cụ & lệnh",
"btn_select_game": "Chọn tệp Game",
"btn_selecting": "Đang chọn...",
"btn_start_game": "Bắt đầu Game",
"btn_game_running": "Game đang chạy",
"menu_change_path": "Thay đổi đường dẫn Game",
"menu_check_update": "Kiểm tra cập nhật Server & Proxy",
"menu_open_server": "Mở thư mục Server",
"menu_open_proxy": "Mở thư mục Proxy",
"menu_open_voice": "Mở thư mục Voice",
"status_updating_launcher": "Đang cập nhật Launcher",
"status_update_success": "Cập nhật thành công, tự động đóng sau 5s",
"status_update_failed": "Cập nhật thất bại, tự động đóng sau 5s",
"status_wait": "Vui lòng đợi...",
"status_complete": "Hoàn tất!",
"modal_update_title": "Cập nhật dữ liệu",
"modal_update_msg": "Bạn có muốn cập nhật Server và Proxy không?",
"modal_download_title": "Tải dữ liệu",
"modal_download_msg": "Yêu cầu tải xuống dữ liệu Server và Proxy",
"modal_self_update_title": "Cập nhật Launcher",
"modal_self_update_msg": "Bạn có muốn cập nhật Launcher không?",
"btn_yes": "Có",
"btn_no": "Không",
"btn_download": "Tải xuống",
"error_game_dir": "Thư mục Game không hợp lệ",
"error_file_type": "Định dạng tệp không đúng",
"game_path_success": "Đã lưu đường dẫn Game thành công",
"no_updates": "Không có bản cập nhật nào",
"toast_pick_folder_error": "Lỗi chọn thư mục: ",
"toast_start_proxy_failed": "Khởi động proxy thất bại: ",
"toast_start_server_failed": "Khởi động server thất bại: ",
"toast_start_game_failed": "Khởi động game thất bại: ",
"toast_start_game_error": "Lỗi khởi động game: "
},
"diff": {
"header_title": "🎮 Cập nhật Game bằng Diff Tool",
"header_desc": "Hỗ trợ bạn cập nhật game bằng Diff Tool",
"game_dir_title": "Thư mục Game",
"btn_selecting": "Đang chọn...",
"btn_select_game": "Chọn Thư mục Game",
"game_dir_valid": "Đã tìm thấy thư mục game hợp lệ!",
"game_dir_invalid": "Không tìm thấy thư mục game. Vui lòng chọn đúng thư mục.",
"diff_file_title": "Tệp Diff",
"btn_select_diff": "Chọn Tệp Diff",
"diff_file_valid": "Đã tìm thấy tệp diff hợp lệ!",
"diff_file_invalid": "Không tìm thấy tệp diff. Vui lòng chọn đúng tệp.",
"btn_updating": "Đang cập nhật...",
"btn_update_game": "Cập nhật Game",
"modal_update_title": "Cập nhật Game",
"status_wait": "Vui lòng đợi...",
"inst_title": "📋 Hướng dẫn:",
"inst_step_1": "1. Nhấn \"Chọn Thư mục Game\" và chọn thư mục gốc của game",
"inst_step_2": "2. Đợi hệ thống xác thực thư mục game",
"inst_step_3": "3. Nhấn \"Chọn Tệp Diff\" và chọn tệp diff của bạn",
"inst_step_4": "4. Đợi hệ thống xác thực tệp diff",
"inst_step_5": "5. Nhấn \"Cập nhật Game\" để bắt đầu quá trình",
"toast_game_dir_not_found": "Không tìm thấy thư mục game. Vui lòng chọn đúng thư mục.",
"toast_no_folder_selected": "Chưa chọn đường dẫn thư mục",
"toast_pick_folder_error": "Lỗi chọn thư mục: ",
"toast_invalid_file_type": "Định dạng tệp không hợp lệ",
"toast_no_file_selected": "Chưa chọn đường dẫn tệp",
"toast_pick_file_error": "Lỗi chọn tệp: ",
"toast_select_both": "Vui lòng chọn cả thư mục game và tệp diff",
"toast_update_completed": "Cập nhật game hoàn tất",
"stage_check_type": "Kiểm tra loại Diff Tool",
"stage_version_validate": "Xác thực phiên bản",
"stage_data_extract": "Trích xuất dữ liệu",
"stage_cut_data": "Cắt dữ liệu",
"stage_patch_data": "Vá dữ liệu",
"stage_delete_old_files": "Xóa tệp cũ"
},
"language": {
"header_title": "🎮 Quản lý ngôn ngữ Game",
"header_desc": "Quản lý cài đặt ngôn ngữ văn bản và giọng nói cho game của bạn",
"game_dir_title": "Thư mục Game",
"btn_select_game": "Chọn Thư mục Game",
"btn_selecting": "Đang chọn...",
"game_dir_valid": "Đã tìm thấy thư mục game hợp lệ!",
"game_dir_invalid": "Không tìm thấy thư mục game. Vui lòng chọn đúng thư mục.",
"current_languages_title": "Ngôn ngữ hiện tại",
"text_language": "Ngôn ngữ văn bản",
"voice_language": "Ngôn ngữ giọng nói",
"language_settings_title": "Cài đặt ngôn ngữ",
"select_text_placeholder": "Chọn ngôn ngữ văn bản...",
"select_voice_placeholder": "Chọn ngôn ngữ giọng nói...",
"btn_apply": "Áp dụng Cài đặt Ngôn ngữ",
"btn_applying": "Đang áp dụng...",
"inst_title": "📋 Hướng dẫn:",
"inst_step_1": "1. Nhấn \"Chọn Thư mục Game\" và chọn thư mục gốc của game",
"inst_step_2": "2. Đợi hệ thống xác thực thư mục game",
"inst_step_3": "3. Chọn ngôn ngữ văn bản và giọng nói bạn muốn",
"inst_step_4": "4. Nhấn \"Áp dụng Cài đặt Ngôn ngữ\" để lưu thay đổi",
"toast_no_folder_selected": "Chưa chọn đường dẫn thư mục",
"toast_pick_folder_error": "Lỗi chọn thư mục: ",
"toast_set_language_success": "Cài đặt ngôn ngữ thành công",
"toast_set_language_error": "Lỗi cài đặt ngôn ngữ: "
},
"howto": {
"title": "Hướng dẫn sử dụng",
"sect1_title": "Sử dụng các tính năng của Launcher",
"sect1_auto_update_pre": "Tự động cập nhật",
"sect1_auto_update_post": "và các công cụ proxy khi khởi chạy.",
"sect1_launch_game": "Khởi chạy game trực tiếp thông qua launcher với các thông số và môi trường chạy chính xác.",
"sect1_lang_pre": "Hỗ trợ chuyển đổi ngôn ngữ trong game (ví dụ: EN, JP, ZH, KR) qua",
"sect1_lang_link": "Công cụ Ngôn ngữ",
"sect1_patch_title": "Vá & Cập nhật Tệp Game",
"sect1_patch_desc_1_pre": "Sử dụng",
"sect1_patch_desc_1_link": "Công cụ Diff",
"sect1_patch_desc_1_post": "(DiffPatch) để cập nhật gia tăng nhanh chóng & nhẹ nhàng.",
"sect1_patch_desc_2_pre": "Hỗ trợ",
"sect1_patch_desc_2_post": "và các định dạng diff tùy chỉnh.",
"sect2_title": "Lệnh Chat FireflyGo",
"sect2_desc_pre": "Dưới đây là các lệnh chat trong game bạn có thể sử dụng. Một số lệnh yêu cầu bạn phải bật",
"sect2_desc_bold": "Chế độ Theorycraft",
"sect2_desc_post": ".",
"sect2_tc_req_title": "Yêu cầu Chế độ Theorycraft",
"sect2_tc_req_desc_pre": "Các lệnh sau chỉ khả dụng khi",
"sect2_tc_req_desc_bold": "Chế độ Theorycraft",
"sect2_tc_req_desc_post": "được bật:",
"sect2_extra_title": "Cài đặt Bổ sung",
"sect2_hidden_ui_title": "Ẩn UI",
"sect2_hidden_ui_desc": "Ẩn ngay lập tức toàn bộ giao diện game — thường được dùng trong các video showcase DIM.",
"sect2_censor_title": "Tắt Kiểm duyệt",
"sect2_censor_desc": "Loại bỏ hiệu ứng kiểm duyệt Lens Flare 💀 để có trải nghiệm rõ ràng hơn.",
"sect2_tc_title": "Chế độ Theorycraft",
"sect2_tc_desc": "Không cần gõ lệnh chat nữa — cấu hình mọi thứ qua web: điều chỉnh HP quái, đặt chu kỳ, xem log, và hơn thế nữa.",
"sect2_cmd_title": "Các lệnh khả dụng:",
"sect2_cmd_tc": "Chế độ Theorycraft",
"sect2_cmd_tc_enable": "— Bật Chế độ Theorycraft",
"sect2_cmd_tc_disable": "— Tắt Chế độ Theorycraft",
"sect2_cmd_cycle": "Điều khiển Chu kỳ",
"sect2_cmd_tc_only": "(Chỉ Theorycraft)",
"sect2_cmd_cycle_desc": "— Đặt số chu kỳ trong trận đấu",
"sect2_cmd_cycle_ex1_pre": "Ví dụ:",
"sect2_cmd_cycle_ex1_post": "đặt trận đấu thành 30 chu kỳ",
"sect2_cmd_cycle_ex2_post": "tắt chu kỳ tùy chỉnh",
"sect2_cmd_hp": "Ghi đè HP",
"sect2_cmd_hp_desc1": "— Đặt HP quái vật (chỉ khả dụng trong chế độ Theorycraft)",
"sect2_cmd_hp_desc2": "— Tắt tính năng đặt HP",
"sect2_cmd_hp_desc3": "— Đặt HP cho từng quái vật trong một đợt (wave) cụ thể",
"sect2_cmd_hp_ex_pre": "Ví dụ:",
"sect2_cmd_hp_ex_post": "đặt đợt 1 quái 1 HP=2,000,000 và quái 2 HP=3,000,000",
"sect2_cmd_log": "Nhật ký Trận đấu (Log)",
"sect2_cmd_log_desc1": "— Bật xuất nhật ký trận đấu",
"sect2_cmd_log_desc2": "— Tắt nhật ký trận đấu",
"sect2_cmd_log_out_pre": "Đầu ra sẽ được ghi dưới dạng",
"sect2_cmd_skip": "Bỏ qua Node",
"sect2_cmd_skip_desc": "— Bỏ qua các node trong MOC / AS / Pure Fiction",
"sect2_cmd_skip_ex1_pre": "Ví dụ:",
"sect2_cmd_skip_ex1_post": "bỏ qua node 2",
"sect2_cmd_skip_ex2_post": "tắt tính năng bỏ qua",
"sect2_cmd_id": "Chuyển đổi Vận mệnh Nhân vật",
"sect2_cmd_id_desc": "— Chuyển vận mệnh cho các nhân vật có nhiều form",
"sect2_cmd_id_ex1_pre": "Ví dụ:",
"sect2_cmd_id_ex1_post": "để đổi form MC (Nhà Khai Phá)",
"sect2_cmd_id_ex2_pre": "Hoạt động với các ID như",
"sect2_cmd_update": "Làm mới Dữ liệu",
"sect2_cmd_update_desc_pre": "— Làm mới dữ liệu máy chủ từ tệp",
"sect3_title": "Các Lưu ý Khác",
"sect3_admin_title": "Quyền Quản trị viên (Administrator)",
"sect3_admin_desc": "Luôn chạy launcher dưới quyền Administrator để có quyền truy cập tệp.",
"sect3_backup_title": "Sao lưu Dữ liệu",
"sect3_backup_desc_pre": "Sao lưu tệp",
"sect3_backup_desc_mid": "và",
"sect3_backup_desc_post": "của bạn thường xuyên.",
"sect3_voice_title": "Bật Gói Giọng nói (Voice Packs) trong Client Beta",
"sect3_voice_step1_pre": "Sao chép thư mục giọng nói bạn muốn (ví dụ:",
"sect3_voice_step1_mid": "từ:",
"sect3_voice_step1_post_pre": "vào thư mục beta bằng cách nhấn",
"sect3_voice_step1_post_bold": "\"Mở thư mục Voice\"",
"sect3_voice_step1_post_post": "trên tab Trang chủ.",
"sect3_voice_step2": "Khi khởi chạy game lần đầu, thư mục giọng nói có thể bị xóa. Nếu vậy, hãy lặp lại bước 1 để khôi phục.",
"btn_back": "Trở về Trang chủ"
},
"about": {
"title": "Giới thiệu",
"p1_pre": "Xin chào! Chúng tôi là ",
"p1_post": ", một nhóm phát triển đam mê xây dựng các công cụ hữu ích và cải thiện trải nghiệm người dùng.",
"p2_pre": "Tôi đã tạo ra một ",
"p2_highlight": "Game Launcher",
"p2_post": " hiện đại và gọn nhẹ để giúp người dùng dễ dàng khởi chạy và quản lý game với hiệu suất tốt hơn và đơn giản hơn.",
"p3_pre": "Launcher được xây dựng bằng ",
"p3_mid": ", với giao diện gọn gàng và tốc độ phản hồi nhanh được thiết kế bằng ",
"p3_and": " và ",
"p3_post": ".",
"p4": "Mục tiêu của tôi là tạo ra các công cụ nhanh, hiệu quả và mang lại trải nghiệm sử dụng thú vị — và launcher này chỉ là sự khởi đầu.",
"btn_back": "Trở về Trang chủ"
},
"fireflytools": {
"title": "Công cụ Firefly",
"sect1_title": "Về Công cụ Firefly",
"sect1_p1_pre": "Trang web này là một phiên bản khác của ",
"sect1_p1_tool": "Firefly Tools ",
"sect1_p1_mid": "được phát triển bởi ",
"sect1_p1_author": "Firefly Shelter",
"sect1_master_site": "Trang web Gốc",
"sect1_p2_pre": "Công cụ ban đầu được tạo ra bởi một nhà phát triển bên thứ ba tên là ",
"sect1_p2_author": "Amazing",
"sect1_p2_post": ". Phiên bản này dựa trực tiếp trên công việc đó, không sửa đổi logic cốt lõi.",
"sect1_p3_pre": "Ngoài ra còn có một phiên bản hiện đại hơn của cùng tác giả tại ",
"sect2_title": "Tính năng Chính",
"sect2_feat1": "Cấu hình nhân vật, nón ánh sáng, di vật, vết tích và tinh hồn dễ dàng trên trình duyệt của bạn.",
"sect2_feat2_pre": "Áp dụng ngay các thiết lập vào ",
"sect2_feat2_server": "Máy chủ Firefly GO",
"sect2_feat2_mid": " bằng cách sử dụng ",
"sect2_feat2_conn": "Connect PS",
"sect2_feat2_post": " — không cần tải tệp lên thủ công.",
"sect2_feat3_title": "Cài đặt Bổ sung",
"sect2_feat3_desc_pre": "Nâng cao trải nghiệm ",
"sect2_feat3_desc_server": "Máy chủ Firefly GO",
"sect2_feat3_desc_post": " của bạn với các tính năng bổ sung:",
"sect2_feat3_ui": "Ẩn UI Game",
"sect2_feat3_ui_desc": "— xóa toàn bộ giao diện trò chơi.",
"sect2_feat3_censor": "Tắt Kiểm duyệt",
"sect2_feat3_censor_desc": "— loại bỏ hiệu ứng kiểm duyệt Lens Flare 💀.",
"sect2_feat3_tc": "Chế độ Theorycraft",
"sect2_feat3_tc_desc": "— cấu hình HP, chu kỳ và nhiều tính năng khác qua web.",
"sect2_feat4_pre": "Xuất và nhập toàn bộ bản build bằng ",
"sect2_feat4_post": ".",
"sect2_feat5": "Quy trình thử nghiệm nhanh chóng — không có thời gian chờ đồng bộ, cập nhật ngay lập tức trong game.",
"sect3_title": "Bắt đầu Sử dụng",
"sect3_step1": "Truy cập công cụ thông qua trình duyệt của bạn tại phiên bản tự lưu trữ (self-hosted).",
"sect3_step2": "Cấu hình bản build nhân vật của bạn với giao diện web trực quan.",
"sect3_step3_pre": "Sử dụng tính năng ",
"sect3_step3_bold": "Connect PS",
"sect3_step3_post": " để đồng bộ tức thì với máy chủ riêng của bạn.",
"sect3_step4": "Thử nghiệm bản build trong game với các bản cập nhật và sửa đổi theo thời gian thực.",
"btn_back": "Trở về Trang chủ"
},
"analysis": {
"title": "Phân tích Firefly & Plugin Veritas",
"sect1_title": "Về Veritas",
"sect1_p1_pre": "",
"sect1_p1_mid": " là một ",
"sect1_p1_highlight": "Damage Logger",
"sect1_p1_post": " mạnh mẽ được thiết kế để phân tích sát thương theo thời gian thực trong quá trình chơi game.",
"sect1_p2": "Nó gọn nhẹ, nhanh chóng và dễ sử dụng để phân tích sát thương toàn diện.",
"sect1_github": "Kho lưu trữ GitHub",
"sect2_title": "Công cụ Phân tích Web",
"sect2_desc": "Sử dụng các ứng dụng web này để phân tích sát thương thời gian thực với Veritas:",
"sect2_master": "Trang web Gốc",
"sect2_backup": "Trang web Dự phòng",
"sect2_tip_pre": "Mẹo:",
"sect2_tip_post": " Nếu quốc gia của bạn gặp sự cố khi tải trang web gốc, vui lòng sử dụng trang web dự phòng.",
"sect3_title": "Hướng dẫn Cài đặt",
"sect3_subtitle": "Bước Thiết lập Quan trọng",
"sect3_desc": "Sau khi tải xuống Veritas, bạn phải đổi tên tệp để nó hoạt động bình thường:",
"sect3_rename": "Đổi tên: ",
"sect3_place": "Sau đó đặt ",
"sect3_place_post": " vào thư mục game của bạn.",
"sect4_title": "Cách sử dụng Web App",
"sect4_sub1": "Dành cho Firefly GO Local",
"sect4_sub1_step1_pre": "Khởi chạy ",
"sect4_sub1_step1_game": "game",
"sect4_sub1_step1_mid": " và ",
"sect4_sub1_step1_server": "Máy chủ Firefly GO (PS)",
"sect4_sub1_step1_post": " của bạn.",
"sect4_sub1_step2": "Mở một trong các công cụ phân tích web.",
"sect4_sub1_step3_pre": "Đi tới ",
"sect4_sub1_step3_conn": "Cài đặt Kết nối",
"sect4_sub1_step3_mid1": " → chọn ",
"sect4_sub1_step3_type": "Loại Kết nối: PS",
"sect4_sub1_step3_mid2": " → nhấn ",
"sect4_sub1_step3_btn": "Kết nối",
"sect4_sub1_step3_post": ".",
"sect4_sub1_step4": "Sau khi kết nối, hãy chơi game. Công cụ sẽ tự động phân tích ở chế độ nền.",
"sect4_sub2": "Dành cho các Máy chủ riêng Khác",
"sect4_sub2_step1_pre": "Khởi chạy ",
"sect4_sub2_step1_game": "game",
"sect4_sub2_step1_mid": " và ",
"sect4_sub2_step1_server": "Máy chủ riêng",
"sect4_sub2_step1_post": " của bạn.",
"sect4_sub2_step2": "Mở một trong các công cụ phân tích web.",
"sect4_sub2_step3_pre": "Đi tới ",
"sect4_sub2_step3_conn": "Cài đặt Kết nối",
"sect4_sub2_step3_mid1": " → chọn ",
"sect4_sub2_step3_type": "Loại Kết nối: Native",
"sect4_sub2_step3_mid2": " → nhấn ",
"sect4_sub2_step3_btn": "Kết nối",
"sect4_sub2_step3_post": ".",
"sect4_sub2_step4": "Sau khi kết nối, hãy chơi game bình thường.",
"btn_back": "Trở về Trang chủ"
}
}

View File

@@ -0,0 +1,337 @@
{
"header": {
"home": "首页",
"tools": "工具",
"language": "语言",
"diff": "差异",
"client_update": "客户端更新",
"plugins": "插件",
"analysis": "分析Veritas",
"firefly_tools": "Firefly工具",
"how_to": "使用方法",
"about": "关于",
"settings": "设置",
"minimize": "最小化",
"close": "关闭",
"by": "Firefly Shelter"
},
"background": {
"select_bg": "选择背景",
"choose_bg": "选择背景",
"paste_url": "粘贴图片链接 (https://...)",
"add": "添加",
"upload": "上传",
"upload_from_computer": "从电脑上传",
"done": "完成",
"remove": "删除",
"invalid_url": "无效的URL"
},
"close": {
"title": "确认操作",
"description": "您是要最小化到系统托盘还是关闭应用程序?",
"dont_ask": "不再提示",
"minimize": "最小化",
"close": "关闭"
},
"setting": {
"title": "设置",
"launcher_update_title": "启动器更新",
"launcher_update_desc": "检查您的启动器是否为最新版本。",
"launcher_update_btn": "检查更新",
"launcher_update_success": "启动器已是最新版本",
"closing_options_title": "关闭选项",
"set_dont_ask_again": "不再提示",
"closing_auto_desc": "下次关闭时,将自动{{action}}而不进行询问。",
"action_minimize": "最小化到系统托盘",
"action_quit": "退出应用程序",
"version_label": "版本",
"server_label": "Server",
"proxy_label": "Proxy",
"launcher_label": "Launcher"
},
"home": {
"header_title": "Firefly GO",
"tooltip_how_to": "如何使用工具与命令",
"btn_select_game": "选择游戏文件",
"btn_selecting": "正在选择...",
"btn_start_game": "开始游戏",
"btn_game_running": "游戏正在运行",
"menu_change_path": "更改游戏路径",
"menu_check_update": "检查服务器与代理更新",
"menu_open_server": "打开服务器文件夹",
"menu_open_proxy": "打开代理文件夹",
"menu_open_voice": "打开语音文件夹",
"status_updating_launcher": "正在更新启动器",
"status_update_success": "更新成功5秒后自动关闭",
"status_update_failed": "更新失败5秒后自动关闭",
"status_wait": "请稍候...",
"status_complete": "完成!",
"modal_update_title": "更新数据",
"modal_update_msg": "是否要更新服务器和代理数据?",
"modal_download_title": "下载数据",
"modal_download_msg": "需要下载服务器和代理数据",
"modal_self_update_title": "更新启动器",
"modal_self_update_msg": "是否要更新启动器?",
"btn_yes": "是",
"btn_no": "否",
"btn_download": "下载",
"error_game_dir": "无效的游戏目录",
"error_file_type": "文件类型错误",
"game_path_success": "游戏路径设置成功",
"no_updates": "暂无更新",
"toast_pick_folder_error": "选择文件夹错误: ",
"toast_start_proxy_failed": "启动代理失败: ",
"toast_start_server_failed": "启动服务器失败: ",
"toast_start_game_failed": "启动游戏失败: ",
"toast_start_game_error": "启动游戏错误: "
},
"diff": {
"header_title": "🎮 使用 Diff Tool 更新游戏",
"header_desc": "帮助您使用 Diff Tool 轻松更新游戏",
"game_dir_title": "游戏目录",
"btn_selecting": "正在选择...",
"btn_select_game": "选择游戏文件夹",
"game_dir_valid": "找到有效的游戏目录!",
"game_dir_invalid": "未找到游戏目录,请选择正确的文件夹。",
"diff_file_title": "Diff 文件目录",
"btn_select_diff": "选择 Diff 文件",
"diff_file_valid": "找到有效的 Diff 文件!",
"diff_file_invalid": "未找到 Diff 文件,请选择正确的文件。",
"btn_updating": "正在更新...",
"btn_update_game": "更新游戏",
"modal_update_title": "更新游戏",
"status_wait": "请稍候...",
"inst_title": "📋 使用说明:",
"inst_step_1": "1. 点击“选择游戏文件夹”并选择您的游戏根目录",
"inst_step_2": "2. 等待系统验证游戏目录",
"inst_step_3": "3. 点击“选择 Diff 文件”并选择您的差异文件",
"inst_step_4": "4. 等待系统验证 Diff 文件",
"inst_step_5": "5. 点击“更新游戏”以保存更改",
"toast_game_dir_not_found": "未找到游戏目录,请选择正确的文件夹。",
"toast_no_folder_selected": "未选择文件夹路径",
"toast_pick_folder_error": "选择文件夹错误:",
"toast_invalid_file_type": "无效的文件类型",
"toast_no_file_selected": "未选择文件路径",
"toast_pick_file_error": "选择文件错误:",
"toast_select_both": "请同时选择游戏目录和 Diff 文件",
"toast_update_completed": "游戏更新完成",
"stage_check_type": "检查 Diff Tool 类型",
"stage_version_validate": "验证版本",
"stage_data_extract": "提取数据",
"stage_cut_data": "裁剪数据",
"stage_patch_data": "修补数据",
"stage_delete_old_files": "删除旧文件"
},
"language": {
"header_title": "🎮 游戏语言管理器",
"header_desc": "管理游戏的文本和语音语言设置",
"game_dir_title": "游戏目录",
"btn_select_game": "选择游戏文件夹",
"btn_selecting": "正在选择...",
"game_dir_valid": "找到有效的游戏目录!",
"game_dir_invalid": "未找到游戏目录,请选择正确的文件夹。",
"current_languages_title": "当前语言",
"text_language": "文本语言",
"voice_language": "语音语言",
"language_settings_title": "语言设置",
"select_text_placeholder": "选择文本语言...",
"select_voice_placeholder": "选择语音语言...",
"btn_apply": "应用语言设置",
"btn_applying": "正在应用...",
"inst_title": "📋 使用说明:",
"inst_step_1": "1. 点击“选择游戏文件夹”并选择您的游戏根目录",
"inst_step_2": "2. 等待系统验证游戏目录",
"inst_step_3": "3. 选择您偏好的文本和语音语言",
"inst_step_4": "4. 点击“应用语言设置”以保存更改",
"toast_no_folder_selected": "未选择文件夹路径",
"toast_pick_folder_error": "选择文件夹错误:",
"toast_set_language_success": "语言设置成功",
"toast_set_language_error": "设置语言错误:"
},
"howto": {
"title": "使用说明",
"sect1_title": "使用启动器功能",
"sect1_auto_update_pre": "启动时自动更新",
"sect1_auto_update_post": "及代理工具。",
"sect1_launch_game": "使用正确的参数和运行环境,直接通过启动器启动游戏。",
"sect1_lang_pre": "支持切换游戏内语言例如EN, JP, ZH, KR通过",
"sect1_lang_link": "语言工具",
"sect1_patch_title": "修补与更新游戏文件",
"sect1_patch_desc_1_pre": "使用",
"sect1_patch_desc_1_link": "Diff Tool",
"sect1_patch_desc_1_post": "DiffPatch进行快速轻量的增量更新。",
"sect1_patch_desc_2_pre": "支持",
"sect1_patch_desc_2_post": "以及自定义 diff 格式。",
"sect2_title": "FireflyGo 聊天指令",
"sect2_desc_pre": "以下是您可以在游戏中使用的聊天指令。部分指令需要您开启",
"sect2_desc_bold": "Theorycraft 模式",
"sect2_desc_post": "。",
"sect2_tc_req_title": "需要开启 Theorycraft 模式",
"sect2_tc_req_desc_pre": "以下指令仅在",
"sect2_tc_req_desc_bold": "Theorycraft 模式",
"sect2_tc_req_desc_post": "开启时可用:",
"sect2_extra_title": "额外设置",
"sect2_hidden_ui_title": "隐藏 UI",
"sect2_hidden_ui_desc": "立即隐藏整个游戏UI — 常用于 DIM 演示视频中。",
"sect2_censor_title": "禁用和谐",
"sect2_censor_desc": "移除镜头光晕和谐效果 💀,获得更纯净的体验。",
"sect2_tc_title": "Theorycraft 模式",
"sect2_tc_desc": "再也不需要输入聊天指令了 — 通过网页配置一切调整怪物HP、设置轮数、查看日志等。",
"sect2_cmd_title": "可用指令:",
"sect2_cmd_tc": "Theorycraft 模式",
"sect2_cmd_tc_enable": "— 启用 Theorycraft 模式",
"sect2_cmd_tc_disable": "— 禁用 Theorycraft 模式",
"sect2_cmd_cycle": "轮数控制",
"sect2_cmd_tc_only": "(仅 Theorycraft 模式)",
"sect2_cmd_cycle_desc": "— 设置战斗中的轮数",
"sect2_cmd_cycle_ex1_pre": "示例:",
"sect2_cmd_cycle_ex1_post": "将战斗设置为30轮",
"sect2_cmd_cycle_ex2_post": "禁用自定义轮数",
"sect2_cmd_hp": "HP 覆写",
"sect2_cmd_hp_desc1": "— 设置怪物 HP仅在 Theorycraft 模式下可用)",
"sect2_cmd_hp_desc2": "— 禁用 HP 设置功能",
"sect2_cmd_hp_desc3": "— 为特定波次中的每个怪物设置 HP",
"sect2_cmd_hp_ex_pre": "示例:",
"sect2_cmd_hp_ex_post": "设置第 1 波次怪物 1 的 HP=2,000,000怪物 2 的 HP=3,000,000",
"sect2_cmd_log": "战斗日志",
"sect2_cmd_log_desc1": "— 启用战斗日志输出",
"sect2_cmd_log_desc2": "— 禁用战斗日志",
"sect2_cmd_log_out_pre": "输出将被写入为",
"sect2_cmd_skip": "跳过节点",
"sect2_cmd_skip_desc": "— 跳过 混沌回忆 / 虚构叙事 中的节点",
"sect2_cmd_skip_ex1_pre": "示例:",
"sect2_cmd_skip_ex1_post": "跳过节点 2",
"sect2_cmd_skip_ex2_post": "禁用跳过",
"sect2_cmd_id": "角色命途切换",
"sect2_cmd_id_desc": "— 为多形态角色切换命途",
"sect2_cmd_id_ex1_pre": "示例:",
"sect2_cmd_id_ex1_post": "更改开拓者的形态",
"sect2_cmd_id_ex2_pre": "支持的 ID 如",
"sect2_cmd_update": "刷新数据",
"sect2_cmd_update_desc_pre": "— 从当前文件刷新服务器数据:",
"sect3_title": "其他注意事项",
"sect3_admin_title": "管理员权限",
"sect3_admin_desc": "请始终以管理员身份运行启动器,以获取文件权限。",
"sect3_backup_title": "备份数据",
"sect3_backup_desc_pre": "请定期备份您的",
"sect3_backup_desc_mid": "和",
"sect3_backup_desc_post": "。",
"sect3_voice_title": "在 Beta 客户端中启用语音包",
"sect3_voice_step1_pre": "将所需的语音文件夹(例如:",
"sect3_voice_step1_mid": ")从:",
"sect3_voice_step1_post_pre": "复制到 beta 文件夹中,在首页点击",
"sect3_voice_step1_post_bold": "“打开语音文件夹”",
"sect3_voice_step1_post_post": "即可。",
"sect3_voice_step2": "首次启动游戏时可能会删除语音文件夹。如果发生这种情况,请重复步骤 1 进行恢复。",
"btn_back": "返回主页"
},
"about": {
"title": "关于",
"p1_pre": "你好!我们是 ",
"p1_post": ",一个致力于构建实用工具并改善用户体验的开发者团队。",
"p2_pre": "我创建了一个轻量且现代的 ",
"p2_highlight": "游戏启动器",
"p2_post": ",旨在帮助用户以更好的性能和极简的操作轻松启动与管理他们的游戏。",
"p3_pre": "启动器使用 ",
"p3_mid": " 构建,并采用 ",
"p3_and": " 和 ",
"p3_post": " 设计了简洁响应式的界面。",
"p4": "我的目标是制作快速、高效且使用起来令人愉悦的工具 —— 而这个启动器仅仅是个开始。",
"btn_back": "返回主页"
},
"fireflytools": {
"title": "Firefly 工具",
"sect1_title": "关于 Firefly 工具",
"sect1_p1_pre": "本网站是 ",
"sect1_p1_tool": "Firefly Tools ",
"sect1_p1_mid": "的另一个版本,由 ",
"sect1_p1_author": "Firefly Shelter",
"sect1_master_site": "主站",
"sect1_p2_pre": "原始工具由名为 ",
"sect1_p2_author": "Amazing",
"sect1_p2_post": " 的第三方开发者创建。此版本直接基于该作品,未修改核心逻辑。",
"sect1_p3_pre": "原作者还有一个更现代的版本,请访问:",
"sect2_title": "主要功能",
"sect2_feat1": "在浏览器中轻松配置角色、光锥、遗器、行迹和星魂。",
"sect2_feat2_pre": "使用 ",
"sect2_feat2_server": "Connect PS",
"sect2_feat2_mid": " 立即将设置应用到 ",
"sect2_feat2_conn": "Firefly GO Server",
"sect2_feat2_post": " —— 无需手动上传文件。",
"sect2_feat3_title": "额外设置",
"sect2_feat3_desc_pre": "使用额外功能增强您的 ",
"sect2_feat3_desc_server": "Firefly GO Server",
"sect2_feat3_desc_post": " 体验:",
"sect2_feat3_ui": "隐藏游戏 UI",
"sect2_feat3_ui_desc": "—— 移除整个游戏界面。",
"sect2_feat3_censor": "禁用和谐",
"sect2_feat3_censor_desc": "—— 移除镜头光晕和谐 💀。",
"sect2_feat3_tc": "Theorycraft 模式",
"sect2_feat3_tc_desc": "—— 通过网页配置 HP、轮数等。",
"sect2_feat4_pre": "使用 ",
"sect2_feat4_post": " 导入和导出完整配置。",
"sect2_feat5": "快速测试工作流 —— 无同步冷却,游戏内即时更新。",
"sect3_title": "快速开始",
"sect3_step1": "通过浏览器访问自托管实例中的工具。",
"sect3_step2": "使用直观的 Web 界面配置您的角色搭配。",
"sect3_step3_pre": "使用 ",
"sect3_step3_bold": "Connect PS",
"sect3_step3_post": " 功能立即与您的私服同步。",
"sect3_step4": "通过实时更新和修改在游戏中测试您的搭配。",
"btn_back": "返回主页"
},
"analysis": {
"title": "Firefly 分析 & Veritas 插件",
"sect1_title": "关于 Veritas",
"sect1_p1_pre": "Veritas",
"sect1_p1_mid": " 是一款强大的",
"sect1_p1_highlight": "伤害记录器",
"sect1_p1_post": ",专为在游戏过程中实时分析伤害而设计。",
"sect1_p2": "它轻量、快速且易于使用,适合全面的伤害分析。",
"sect1_github": "GitHub 仓库",
"sect2_title": "Web 分析工具",
"sect2_desc": "使用这些 Web 应用程序与 Veritas 进行实时伤害分析:",
"sect2_master": "主站",
"sect2_backup": "备用网站",
"sect2_tip_pre": "提示:",
"sect2_tip_post": " 如果您的国家加载主站有困难,请改用备用网站。",
"sect3_title": "安装说明",
"sect3_subtitle": "重要设置步骤",
"sect3_desc": "下载 Veritas 后,您必须重命名该文件才能正常工作:",
"sect3_rename": "重命名:",
"sect3_place": "然后将 ",
"sect3_place_post": " 放入您的游戏目录。",
"sect4_title": "如何使用 Web 应用",
"sect4_sub1": "适用于 Firefly GO 本地端",
"sect4_sub1_step1_pre": "启动 ",
"sect4_sub1_step1_game": "游戏",
"sect4_sub1_step1_mid": " 和您的 ",
"sect4_sub1_step1_server": "Firefly GO 服务器 (PS)",
"sect4_sub1_step1_post": "。",
"sect4_sub1_step2": "打开其中一个 Web 分析工具。",
"sect4_sub1_step3_pre": "转到 ",
"sect4_sub1_step3_conn": "Connection Settings",
"sect4_sub1_step3_mid1": " → 选择 ",
"sect4_sub1_step3_type": "Connection Type: PS",
"sect4_sub1_step3_mid2": " → 点击 ",
"sect4_sub1_step3_btn": "Connect",
"sect4_sub1_step3_post": "。",
"sect4_sub1_step4": "连接后,游玩游戏。工具将在后台自动进行分析。",
"sect4_sub2": "适用于其他私服",
"sect4_sub2_step1_pre": "启动 ",
"sect4_sub2_step1_game": "游戏",
"sect4_sub2_step1_mid": " 和您的 ",
"sect4_sub2_step1_server": "私服",
"sect4_sub2_step1_post": "。",
"sect4_sub2_step2": "打开其中一个 Web 分析工具。",
"sect4_sub2_step3_pre": "转到 ",
"sect4_sub2_step3_conn": "Connection Settings",
"sect4_sub2_step3_mid1": " → 选择 ",
"sect4_sub2_step3_type": "Connection Type: Native",
"sect4_sub2_step3_mid2": " → 点击 ",
"sect4_sub2_step3_btn": "Connect",
"sect4_sub2_step3_post": "。",
"sect4_sub2_step4": "连接后,正常游玩游戏即可。",
"btn_back": "返回主页"
}
}

View File

@@ -5,6 +5,8 @@ import { X, Image as ImageIcon, Plus, Upload, Check } from 'lucide-react'
import useSettingStore from '@/stores/settingStore' import useSettingStore from '@/stores/settingStore'
import Cropper from 'react-easy-crop' import Cropper from 'react-easy-crop'
import getCroppedImg from '@/utils/cropImage' import getCroppedImg from '@/utils/cropImage'
import { useTranslation } from 'react-i18next'
import { toast } from "react-toastify"
const initialImages = { const initialImages = {
"bg-1": "bg-1.jpeg", "bg-1": "bg-1.jpeg",
@@ -30,14 +32,26 @@ export const BackgroundSelector = () => {
const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null) const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null)
const { background, setBackground, extraBackgrounds, setExtraBackgrounds } = useSettingStore() const { background, setBackground, extraBackgrounds, setExtraBackgrounds } = useSettingStore()
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const { t } = useTranslation()
const handleSelect = (img: string) => { const handleSelect = (img: string) => {
setIsOpen(false) setIsOpen(false)
setBackground(img) setBackground(img)
} }
const isImageUrl = (url: string) => {
return /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif)(\?.*)?$/i.test(url)
}
const handleAddUrl = () => { const handleAddUrl = () => {
if (!newUrl.trim()) return setCroppingImage(newUrl) const url = newUrl.trim()
if (!url) {
return
}
if (!isImageUrl(url)) {
toast.error(t("background.invalid_url"))
return
}
setCroppingImage(url)
setNewUrl('') setNewUrl('')
} }
@@ -61,8 +75,8 @@ export const BackgroundSelector = () => {
const allBackgrounds = [...extraBackgrounds, ...Object.values(initialImages)] const allBackgrounds = [...extraBackgrounds, ...Object.values(initialImages)]
return ( return (
<div className="flex flex-col items-center justify-center gap-4"> <div className="flex flex-col items-center justify-center gap-4 w-full">
<div className="tooltip tooltip-right" data-tip="Select Background"> <div className="tooltip tooltip-right" data-tip={t("background.select_bg")}>
<button <button
className="group btn btn-primary btn-circle flex items-center justify-center shadow-md transition-all duration-300 hover:scale-110 hover:shadow-lg hover:bg-primary/80" className="group btn btn-primary btn-circle flex items-center justify-center shadow-md transition-all duration-300 hover:scale-110 hover:shadow-lg hover:bg-primary/80"
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
@@ -78,19 +92,19 @@ export const BackgroundSelector = () => {
<X size={20} /> <X size={20} />
</button> </button>
<h2 className="text-lg font-semibold mb-4">Choose Background</h2> <h2 className="text-lg font-semibold mb-4">{t("background.choose_bg")}</h2>
{/* Add via URL */} {/* Add via URL */}
<div className="flex gap-2 mb-4"> <div className="flex gap-2 mb-4">
<input <input
type="text" type="text"
placeholder="Paste image URL (https://...)" placeholder={t("background.paste_url")}
className="input input-bordered w-full text-info" className="input input-bordered w-full text-info"
value={newUrl} value={newUrl}
onChange={(e) => setNewUrl(e.target.value)} onChange={(e) => setNewUrl(e.target.value)}
/> />
<button className="btn btn-success flex items-center gap-1" onClick={handleAddUrl}> <button className="btn btn-success flex items-center gap-1" onClick={handleAddUrl}>
<Plus size={16} /> Add <Plus size={16} /> {t("background.add")}
</button> </button>
</div> </div>
@@ -100,7 +114,7 @@ export const BackgroundSelector = () => {
className="btn btn-warning flex items-center gap-1" className="btn btn-warning flex items-center gap-1"
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
> >
<Upload size={16} /> Upload from computer <Upload size={16} /> {t("background.upload_from_computer")}
</button> </button>
<input <input
type="file" type="file"
@@ -116,9 +130,9 @@ export const BackgroundSelector = () => {
</div> </div>
{/* Crop Modal */} {/* Crop Modal */}
{croppingImage && ( {(croppingImage != null && croppingImage != "") && (
<div className="fixed inset-0 z-60 flex flex-col items-center justify-center bg-black/70 p-4"> <div className="fixed inset-0 z-60 flex flex-col items-center justify-center bg-black/70 p-4">
<div className="relative w-full max-w-3xl h-[400px] bg-gray-800 rounded-lg"> <div className="relative w-full max-w-5xl h-150 bg-gray-800 rounded-lg">
<Cropper <Cropper
image={croppingImage} image={croppingImage}
crop={crop} crop={crop}
@@ -132,7 +146,7 @@ export const BackgroundSelector = () => {
className="absolute bottom-4 left-1/2 -translate-x-1/2 btn btn-success" className="absolute bottom-4 left-1/2 -translate-x-1/2 btn btn-success"
onClick={handleCropComplete} onClick={handleCropComplete}
> >
<Check size={20} /> Done <Check size={20} /> {t("background.done")}
</button> </button>
<button <button
className="absolute top-2 right-2 btn btn-ghost btn-circle" className="absolute top-2 right-2 btn btn-ghost btn-circle"
@@ -150,8 +164,7 @@ export const BackgroundSelector = () => {
return ( return (
<div <div
key={i} key={i}
className={`relative rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${ className={`relative rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${value === background ? 'border-blue-500' : 'border-transparent hover:border-gray-500'
value === background ? 'border-blue-500' : 'border-transparent hover:border-gray-500'
}`} }`}
onClick={() => handleSelect(value)} onClick={() => handleSelect(value)}
> >

View File

@@ -2,6 +2,7 @@ import { motion } from "motion/react"
import { AppService } from "@bindings/firefly-launcher/internal/app-service" import { AppService } from "@bindings/firefly-launcher/internal/app-service"
import { toast } from "react-toastify" import { toast } from "react-toastify"
import useSettingStore from "@/stores/settingStore" import useSettingStore from "@/stores/settingStore"
import { useTranslation } from "react-i18next"
export default function CloseModal({ export default function CloseModal({
isOpen, isOpen,
@@ -12,13 +13,14 @@ export default function CloseModal({
}) { }) {
if (!isOpen) return null if (!isOpen) return null
const { closingOption, setClosingOption } = useSettingStore() const { closingOption, setClosingOption } = useSettingStore()
const { t } = useTranslation()
return ( return (
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-2xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="relative w-[90%] max-w-2xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4 flex justify-between items-center"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4 flex justify-between items-center">
<h3 className="font-bold text-xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-600"> <h3 className="font-bold text-xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-600">
Confirm Action {t("close.title")}
</h3> </h3>
<motion.button <motion.button
whileHover={{ scale: 1.1, rotate: 90 }} whileHover={{ scale: 1.1, rotate: 90 }}
@@ -32,7 +34,7 @@ export default function CloseModal({
<div className="px-6 pt-2 pb-6"> <div className="px-6 pt-2 pb-6">
<p className="mb-4 text-lg"> <p className="mb-4 text-lg">
Do you want to minimize the application to the system tray or close the application? {t("close.description")}
</p> </p>
<div className="flex items-center mb-4"> <div className="flex items-center mb-4">
@@ -44,7 +46,7 @@ export default function CloseModal({
onChange={(e) => setClosingOption({ isMinimize: closingOption.isMinimize, isAsk: !e.target.checked })} onChange={(e) => setClosingOption({ isMinimize: closingOption.isMinimize, isAsk: !e.target.checked })}
/> />
<label htmlFor="dontAskAgain" className="text-sm font-semibold text-accent"> <label htmlFor="dontAskAgain" className="text-sm font-semibold text-accent">
Do not ask me again {t("close.dont_ask")}
</label> </label>
</div> </div>
@@ -60,7 +62,7 @@ export default function CloseModal({
} }
}} }}
> >
Minimize {t("close.minimize")}
</button> </button>
<button <button
className="btn btn-error btn-outline" className="btn btn-error btn-outline"
@@ -73,7 +75,7 @@ export default function CloseModal({
} }
}} }}
> >
Close {t("close.close")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -2,26 +2,42 @@ import { Link } from "@tanstack/react-router";
import useModalStore from "@/stores/modalStore"; import useModalStore from "@/stores/modalStore";
import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react"; import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react";
import { AppService } from "@bindings/firefly-launcher/internal/app-service"; import { AppService } from "@bindings/firefly-launcher/internal/app-service";
import LanguageSwitcher from "../languageSwitcher";
import { useTranslation } from "react-i18next";
import useSettingStore from "@/stores/settingStore";
export default function Header() { export default function Header() {
const { setIsOpenSettingModal } = useModalStore() const { setIsOpenSettingModal, setIsOpenCloseModal } = useModalStore()
const { closingOption } = useSettingStore()
const { t } = useTranslation()
const controlButtons = [ const controlButtons = [
{ {
icon: <Settings className="w-5 h-5" />, icon: <Settings className="w-5 h-5" />,
action: () => setIsOpenSettingModal(true), action: () => setIsOpenSettingModal(true),
tip: "Settings", tip: t("header.settings"),
}, },
{ {
icon: <Minus className="w-5 h-5" />, icon: <Minus className="w-5 h-5" />,
action: () => AppService.MinimizeApp(), action: () => AppService.MinimizeApp(),
tip: "Minimize", tip: t("header.minimize"),
}, },
{ {
icon: <X className="w-5 h-5" />, icon: <X className="w-5 h-5" />,
action: () => AppService.CloseApp(), action: () => {
tip: "Close", if (closingOption.isAsk) {
setIsOpenCloseModal(true)
return
}
if (closingOption.isMinimize) {
AppService.HideApp()
return
}
AppService.CloseApp()
}, },
tip: t("header.close"),
},
] ]
return ( return (
@@ -42,26 +58,26 @@ export default function Header() {
tabIndex={0} tabIndex={0}
className="menu menu-sm dropdown-content bg-black/50 backdrop-blur-md rounded-box z-1 mt-3 w-52 p-2 shadow" className="menu menu-sm dropdown-content bg-black/50 backdrop-blur-md rounded-box z-1 mt-3 w-52 p-2 shadow"
> >
<li><Link to="/">Home</Link></li> <li><Link to="/">{t("header.home")}</Link></li>
<li> <li>
<a>Tools</a> <a>{t("header.tools")}</a>
<ul className="p-2"> <ul className="p-2">
<li><Link to="/language">Language</Link></li> <li><Link to="/language">{t("header.language")}</Link></li>
<li><Link to="/diff">Diff</Link></li> <li><Link to="/diff">{t("header.client_update")}</Link></li>
</ul> </ul>
</li> </li>
<li> <li>
<a>Plugins</a> <a>{t("header.plugins")}</a>
<ul className="p-2"> <ul className="p-2">
<li><Link to="/analysis">Analysis (Veritas)</Link></li> <li><Link to="/analysis">{t("header.analysis")}</Link></li>
<li><Link to="/srtools">SrTools</Link></li> <li><Link to="/srtools">{t("header.firefly_tools")}</Link></li>
</ul> </ul>
</li> </li>
<li><Link to="/howto">How to?</Link></li> <li><Link to="/howto">{t("header.how_to")}</Link></li>
<li><Link to="/about">About</Link></li> <li><Link to="/about">{t("header.about")}</Link></li>
</ul> </ul>
</div> </div>
@@ -71,11 +87,11 @@ export default function Header() {
<div className="flex flex-col justify-center items-start"> <div className="flex flex-col justify-center items-start">
<h1 className="text-xl font-bold"> <h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly </span> <span className="text-emerald-500">Firefly </span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500"> <span className="bg-clip-text text-transparent bg-linear-to-r from-emerald-400 via-orange-500 to-red-500">
Launcher Launcher
</span> </span>
</h1> </h1>
<p className="text-white text-sm">By Firefly Shelter</p> <p className="text-white text-sm">{t("header.by")}</p>
</div> </div>
</div> </div>
</Link> </Link>
@@ -89,24 +105,24 @@ export default function Header() {
<ul className="menu menu-horizontal px-1 gap-4 text-white"> <ul className="menu menu-horizontal px-1 gap-4 text-white">
<li> <li>
<Link to="/" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/" className="flex items-center gap-2 hover:text-cyan-300">
<Home size={18} /> Home <Home size={18} /> {t("header.home")}
</Link> </Link>
</li> </li>
<li> <li>
<details> <details>
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300"> <summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 w-full">
<Wrench size={18} /> Tools <Wrench size={18} /> {t("header.tools")}
</summary> </summary>
<ul className="p-2 bg-black/75 text-white rounded-lg"> <ul className="p-2 bg-black/75 text-white rounded-lg min-w-40 whitespace-nowrap">
<li> <li>
<Link to="/language" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/language" className="flex items-center gap-2 hover:text-cyan-300">
<Languages size={18} /> Language <Languages size={18} /> {t("header.language")}
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/diff" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/diff" className="flex items-center gap-2 hover:text-cyan-300">
<Diff size={18} /> Client update <Diff size={18} /> {t("header.client_update")}
</Link> </Link>
</li> </li>
</ul> </ul>
@@ -115,18 +131,18 @@ export default function Header() {
<li> <li>
<details> <details>
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300"> <summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 w-full">
<Puzzle size={18} /> Plugins <Puzzle size={18} /> {t("header.plugins")}
</summary> </summary>
<ul className="p-2 bg-black/75 text-white rounded-lg"> <ul className="p-2 bg-black/75 text-white rounded-lg min-w-40 whitespace-nowrap">
<li> <li>
<Link to="/analysis" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/analysis" className="flex items-center gap-2 hover:text-cyan-300">
<TrendingUpDown size={18} /> Analysis (Veritas) <TrendingUpDown size={18} /> {t("header.analysis")}
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/srtools" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/srtools" className="flex items-center gap-2 hover:text-cyan-300">
<Blend size={18} /> Firefly Tools <Blend size={18} /> {t("header.firefly_tools")}
</Link> </Link>
</li> </li>
</ul> </ul>
@@ -135,13 +151,13 @@ export default function Header() {
<li> <li>
<Link to="/howto" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/howto" className="flex items-center gap-2 hover:text-cyan-300">
<BookOpen size={18} /> How to? <BookOpen size={18} /> {t("header.how_to")}
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/about" className="flex items-center gap-2 hover:text-cyan-300"> <Link to="/about" className="flex items-center gap-2 hover:text-cyan-300">
<Info size={18} /> About <Info size={18} /> {t("header.about")}
</Link> </Link>
</li> </li>
</ul> </ul>
@@ -150,9 +166,10 @@ export default function Header() {
{/* RIGHT */} {/* RIGHT */}
<div <div
className="navbar-end flex gap-2 z-52" className="navbar-end flex gap-2 z-52"
style={{ '--wails-draggable': 'no-drag' } as any}
> >
<div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg"> <div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg" style={{ '--wails-draggable': 'no-drag' } as any}>
<LanguageSwitcher />
{controlButtons.map((btn, i) => ( {controlButtons.map((btn, i) => (
<div key={i} className="tooltip tooltip-bottom" data-tip={btn.tip}> <div key={i} className="tooltip tooltip-bottom" data-tip={btn.tip}>
<button <button

View File

@@ -0,0 +1,110 @@
import { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
const languages = [
{ code: "en", label: "EN", native: "English" },
{ code: "vi", label: "VI", native: "Tiếng Việt" },
{ code: "ja", label: "JA", native: "日本語" },
{ code: "ko", label: "KO", native: "한국어" },
{ code: "zh", label: "ZH", native: "中文" },
];
const LanguageSwitcher = () => {
const { i18n, t } = useTranslation();
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const current = languages.find((l) => l.code === i18n.language) ?? languages[0];
useEffect(() => {
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
const select = (code: string) => {
i18n.changeLanguage(code);
setOpen(false);
};
return (
<div ref={ref} className="relative">
{/* Trigger */}
<button
onClick={() => setOpen((v) => !v)}
className="
btn btn-ghost px-2
flex items-center gap-1
text-sm font-semibold tracking-wider
text-white/80+
select-none
tooltip tooltip-bottom
"
data-tip={t("header.language")}
>
<span className="uppercase">{current.label}</span>
{/* Chevron */}
<svg
className={`w-3 h-3 text-white/50 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2.5}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* Dropdown */}
{open && (
<ul
className="
absolute right-0 mt-2 w-32
bg-black/60 backdrop-blur-md
border border-white/10 rounded-xl
shadow-2xl shadow-black/50
overflow-hidden z-999
py-1
animate-in fade-in slide-in-from-top-2 duration-150
"
>
{languages.map((lang) => {
const isActive = lang.code === i18n.language;
return (
<li key={lang.code}>
<button
onClick={() => select(lang.code)}
className={`
w-full flex items-center gap-3 px-4 py-2.5 text-sm
transition-colors duration-150
${isActive
? "bg-emerald-500/20 text-emerald-400"
: "text-white/70 hover:bg-white/8 hover:text-white"
}
`}
>
<span className="flex-1 text-left">{lang.native}</span>
{isActive && (
<svg className="w-3.5 h-3.5 text-emerald-400 shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
)}
</button>
</li>
);
})}
</ul>
)}
</div>
);
};
export default LanguageSwitcher;

View File

@@ -3,6 +3,7 @@ import useModalStore from "@/stores/modalStore"
import useSettingStore from "@/stores/settingStore" import useSettingStore from "@/stores/settingStore"
import useLauncherStore from "@/stores/launcherStore" import useLauncherStore from "@/stores/launcherStore"
import { toast } from "react-toastify" import { toast } from "react-toastify"
import { useTranslation } from "react-i18next"
export default function SettingModal({ export default function SettingModal({
isOpen, isOpen,
@@ -12,7 +13,7 @@ export default function SettingModal({
onClose: () => void onClose: () => void
}) { }) {
if (!isOpen) return null if (!isOpen) return null
const { t } = useTranslation()
const { setIsOpenSelfUpdateModal } = useModalStore() const { setIsOpenSelfUpdateModal } = useModalStore()
const { closingOption, setClosingOption, serverVersion, const { closingOption, setClosingOption, serverVersion,
proxyVersion, } = useSettingStore() proxyVersion, } = useSettingStore()
@@ -20,7 +21,7 @@ export default function SettingModal({
const CheckUpdate = async () => { const CheckUpdate = async () => {
const launcherData = await CheckUpdateLauncher() const launcherData = await CheckUpdateLauncher()
if (!launcherData.isUpdate) { if (!launcherData.isUpdate) {
toast.success("Launcher is already up to date") toast.success(t("setting.launcher_update_success"))
return return
} }
setUpdateData({ setUpdateData({
@@ -37,8 +38,8 @@ export default function SettingModal({
<div className="relative w-[90%] max-w-md bg-base-100 text-base-content rounded-2xl border border-purple-500/30 shadow-2xl shadow-purple-500/30 p-6"> <div className="relative w-[90%] max-w-md bg-base-100 text-base-content rounded-2xl border border-purple-500/30 shadow-2xl shadow-purple-500/30 p-6">
{/* Header */} {/* Header */}
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h3 className="font-extrabold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-500"> <h3 className="font-extrabold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-500">
Settings {t("setting.title")}
</h3> </h3>
<button <button
className="btn btn-circle btn-sm bg-red-600 hover:bg-red-700 text-white border-none shadow-lg" className="btn btn-circle btn-sm bg-red-600 hover:bg-red-700 text-white border-none shadow-lg"
@@ -52,21 +53,21 @@ export default function SettingModal({
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* Section 1: Launcher Update */} {/* Section 1: Launcher Update */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm"> <div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Launcher Update</h4> <h4 className="font-bold text-lg mb-2">{t("setting.launcher_update_title")}</h4>
<p className="text-sm text-info mb-3"> <p className="text-sm text-info mb-3">
Check if your launcher is up to date. {t("setting.launcher_update_desc")}
</p> </p>
<button <button
className="btn btn-primary bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-400 hover:to-red-500 text-white shadow-md hover:shadow-lg transition-all duration-200" className="btn btn-primary bg-linear-to-r from-orange-500 to-red-500 hover:from-orange-400 hover:to-red-500 text-white shadow-md hover:shadow-lg transition-all duration-200"
onClick={CheckUpdate} onClick={CheckUpdate}
> >
Check for Launcher Updates {t("setting.launcher_update_btn")}
</button> </button>
</div> </div>
{/* Section 2: Closing Option */} {/* Section 2: Closing Option */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm"> <div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Closing Options</h4> <h4 className="font-bold text-lg mb-2">{t("setting.closing_options_title")}</h4>
<label className="flex items-start gap-3 cursor-pointer select-none"> <label className="flex items-start gap-3 cursor-pointer select-none">
<input <input
type="checkbox" type="checkbox"
@@ -81,12 +82,10 @@ export default function SettingModal({
/> />
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-base font-medium text-info"> <span className="text-base font-medium text-info">
Set do not ask again {t("setting.set_dont_ask_again")}
</span> </span>
<span className="text-sm text-accent"> <span className="text-sm text-accent">
Next time you close the app, it will automatically{" "} {t('setting.closing_auto_desc', { action: closingOption.isMinimize ? t('setting.action_minimize') : t('setting.action_quit') })}
{closingOption.isMinimize ? "minimize to system tray" : "quit the app"}{" "}
without asking.
</span> </span>
</div> </div>
</label> </label>
@@ -95,17 +94,17 @@ export default function SettingModal({
{/* Section 3: Launcher Version */} {/* Section 3: Launcher Version */}
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm"> <div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
<h4 className="font-bold text-lg mb-2">Version</h4> <h4 className="font-bold text-lg mb-2">{t("setting.version_label")}</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<p className="text-base text-info"> <p className="text-base text-info">
Server: {serverVersion} {t("setting.server_label")}: {serverVersion}
</p> </p>
<p className="text-base text-info"> <p className="text-base text-info">
Proxy: {proxyVersion} {t("setting.proxy_label")}: {proxyVersion}
</p> </p>
<p className="text-base text-info"> <p className="text-base text-info">
Launcher: {launcherVersion} {t("setting.launcher_label")}: {launcherVersion}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -28,7 +28,7 @@ export default function UpdateModal({ isOpen, title, message, buttons, onClose }
</motion.button> </motion.button>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
{title} {title}
</h3> </h3>
</div> </div>
@@ -46,7 +46,7 @@ export default function UpdateModal({ isOpen, title, message, buttons, onClose }
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className={`btn ${ className={`btn ${
btn.variant === "primary" btn.variant === "primary"
? "btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none" ? "btn-primary bg-linear-to-r from-orange-200 to-red-400 border-none"
: btn.variant === "error" : btn.variant === "error"
? "btn-error" ? "btn-error"
: "btn-outline btn-error" : "btn-outline btn-error"

18
frontend/src/i18n.ts Normal file
View File

@@ -0,0 +1,18 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import httpBackend from "i18next-http-backend";
i18n
.use(httpBackend)
.use(LanguageDetector) // detects browser language
.use(initReactI18next)
.init({
fallbackLng: "en", //fallback language
debug: true,
interpolation: {
escapeValue: false,
},
backend: {
loadPath: `/locales/{{lng}}.json`, //path of the languages
},
});

View File

@@ -5,6 +5,7 @@ import './styles/index.css'
import "../runtime.js" import "../runtime.js"
import "@wailsio/runtime"; import "@wailsio/runtime";
import { routeTree } from './routeTree.gen.js' import { routeTree } from './routeTree.gen.js'
import "./i18n";
const router = createRouter({ routeTree }) const router = createRouter({ routeTree })
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {

View File

@@ -1,31 +1,33 @@
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
export default function AboutPage() { export default function AboutPage() {
const { t } = useTranslation();
return ( return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6"> <div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-6"> <div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-6">
<h1 className="text-4xl font-bold text-primary text-center">About</h1> <h1 className="text-4xl font-bold text-primary text-center">{t("about.title")}</h1>
<div className="space-y-4"> <div className="space-y-4">
<p className="text-lg leading-relaxed"> <p className="text-lg leading-relaxed">
Hello! We are <span className="font-semibold text-error">Firefly Shelter</span>, a developer team passionate about building useful tools and improving user experiences. {t("about.p1_pre")}<span className="font-semibold text-error">Firefly Shelter</span>{t("about.p1_post")}
</p> </p>
<p className="text-lg leading-relaxed"> <p className="text-lg leading-relaxed">
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity. {t("about.p2_pre")}<span className="font-semibold text-success">{t("about.p2_highlight")}</span>{t("about.p2_post")}
</p> </p>
<p className="text-lg leading-relaxed"> <p className="text-lg leading-relaxed">
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-accent">Tailwind CSS</span> and <span className="text-accent">DaisyUI</span>. {t("about.p3_pre")}<span className="font-mono text-info">Go + Wails3</span>{t("about.p3_mid")}<span className="text-accent">Tailwind CSS</span>{t("about.p3_and")}<span className="text-accent">DaisyUI</span>{t("about.p3_post")}
</p> </p>
<p className="text-lg leading-relaxed"> <p className="text-lg leading-relaxed">
My goal is to make tools that are fast, efficient, and enjoyable to use and this launcher is just the beginning. {t("about.p4")}
</p> </p>
</div> </div>
<div className="text-center pt-4"> <div className="text-center pt-4">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link> <Link to="/" className="btn btn-primary btn-wide">{t("about.btn_back")}</Link>
</div> </div>
</div> </div>
</div> </div>
); );
} }

View File

@@ -1,18 +1,20 @@
import { Link } from '@tanstack/react-router'; import { Link } from '@tanstack/react-router';
import { useTranslation } from 'react-i18next';
export default function AnalysisPage() { export default function AnalysisPage() {
const { t } = useTranslation();
return ( return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6"> <div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8"> <div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
<h1 className="text-4xl font-bold text-primary text-center"> <h1 className="text-4xl font-bold text-primary text-center">
Firefly Analysis & Veritas Plugin {t("analysis.title")}
</h1> </h1>
{/* About Veritas Section */}
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg"> <div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
<span>🔬</span> <span>🔬</span>
<span>About Veritas</span> <span>{t("analysis.sect1_title")}</span>
</h2> </h2>
<div className="space-y-4 text-green-700"> <div className="space-y-4 text-green-700">
@@ -20,17 +22,19 @@ export default function AnalysisPage() {
<div className="text-green-600 text-lg"></div> <div className="text-green-600 text-lg"></div>
<div> <div>
<p className="mb-2"> <p className="mb-2">
<span className="font-semibold text-success">Veritas</span> is a powerful{" "} <span className="font-semibold text-success">{t("analysis.sect1_p1_pre")}</span>
<span className="text-info font-mono bg-blue-100 px-2 py-1 rounded">Damage Logger</span> designed for analyzing damage in real-time during gameplay. {t("analysis.sect1_p1_mid")}
<span className="text-info font-mono bg-blue-100 px-2 py-1 rounded">{t("analysis.sect1_p1_highlight")}</span>
{t("analysis.sect1_p1_post")}
</p> </p>
<p className="text-sm">It's lightweight, fast, and easy to use for comprehensive damage analysis.</p> <p className="text-sm">{t("analysis.sect1_p2")}</p>
</div> </div>
</div> </div>
<div className="bg-white border border-green-200 rounded-lg p-4"> <div className="bg-white border border-green-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-green-600 text-lg">📁</span> <span className="text-green-600 text-lg">📁</span>
<span className="font-semibold text-green-800">GitHub Repository</span> <span className="font-semibold text-green-800">{t("analysis.sect1_github")}</span>
</div> </div>
<a <a
href="https://github.com/hessiser/veritas" href="https://github.com/hessiser/veritas"
@@ -44,23 +48,22 @@ export default function AnalysisPage() {
</div> </div>
</div> </div>
{/* Web Analysis Tools Section */}
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg"> <div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
<span>🌐</span> <span>🌐</span>
<span>Web Analysis Tools</span> <span>{t("analysis.sect2_title")}</span>
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
<p className="text-blue-700"> <p className="text-blue-700">
Use these web applications for real-time damage analysis with Veritas: {t("analysis.sect2_desc")}
</p> </p>
<div className="grid md:grid-cols-2 gap-4"> <div className="grid md:grid-cols-2 gap-4">
<div className="bg-white border border-blue-200 rounded-lg p-4"> <div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-blue-600 text-lg">🏆</span> <span className="text-blue-600 text-lg">🏆</span>
<span className="font-semibold text-blue-800">Master Website</span> <span className="font-semibold text-blue-800">{t("analysis.sect2_master")}</span>
</div> </div>
<a <a
href="https://sranalysis.punklorde.org" href="https://sranalysis.punklorde.org"
@@ -75,7 +78,7 @@ export default function AnalysisPage() {
<div className="bg-white border border-blue-200 rounded-lg p-4"> <div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-blue-600 text-lg">🔄</span> <span className="text-blue-600 text-lg">🔄</span>
<span className="font-semibold text-blue-800">Backup Website</span> <span className="font-semibold text-blue-800">{t("analysis.sect2_backup")}</span>
</div> </div>
<a <a
href="https://firefly-sranalysis.vercel.app/" href="https://firefly-sranalysis.vercel.app/"
@@ -83,7 +86,7 @@ export default function AnalysisPage() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
https://firefly-sranalysis.vercel.app/ https://firefly-sranalysis.vercel.app
</a> </a>
</div> </div>
</div> </div>
@@ -92,34 +95,33 @@ export default function AnalysisPage() {
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<div className="text-yellow-600 text-lg">💡</div> <div className="text-yellow-600 text-lg">💡</div>
<p className="text-yellow-800 text-sm"> <p className="text-yellow-800 text-sm">
<strong>Tip:</strong> If your country has issues loading from the master site, please use the backup site instead. <strong>{t("analysis.sect2_tip_pre")}</strong>{t("analysis.sect2_tip_post")}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Installation Instructions */}
<div className="bg-red-50 border-l-4 border-red-400 p-6 rounded-r-lg"> <div className="bg-red-50 border-l-4 border-red-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-red-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-red-800 flex items-center gap-2 mb-4">
<span></span> <span></span>
<span>Installation Instructions</span> <span>{t("analysis.sect3_title")}</span>
</h2> </h2>
<div className="bg-white border border-red-200 rounded-lg p-4"> <div className="bg-white border border-red-200 rounded-lg p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-red-600 text-xl">📋</div> <div className="text-red-600 text-xl">📋</div>
<div> <div>
<h3 className="font-semibold text-red-800 mb-2">Important Setup Step</h3> <h3 className="font-semibold text-red-800 mb-2">{t("analysis.sect3_subtitle")}</h3>
<p className="text-red-700 mb-2"> <p className="text-red-700 mb-2">
After downloading Veritas, you must rename the file for it to work properly: {t("analysis.sect3_desc")}
</p> </p>
<div className="bg-red-100 p-3 rounded-lg"> <div className="bg-red-100 p-3 rounded-lg">
<p className="text-red-800 font-mono text-sm"> <p className="text-red-800 font-mono text-sm">
Rename: <code className="bg-red-200 px-1 py-0.5 rounded">veritas.dll</code> <code className="bg-red-200 px-1 py-0.5 rounded">astrolabe.dll</code> {t("analysis.sect3_rename")}<code className="bg-red-200 px-1 py-0.5 rounded">veritas.dll</code> <code className="bg-red-200 px-1 py-0.5 rounded">astrolabe.dll</code>
</p> </p>
<p className="text-red-800 text-sm mt-1"> <p className="text-red-800 text-sm mt-1">
Then place <code className="bg-red-200 px-1 py-0.5 rounded">astrolabe.dll</code> into your game directory. {t("analysis.sect3_place")}<code className="bg-red-200 px-1 py-0.5 rounded">astrolabe.dll</code>{t("analysis.sect3_place_post")}
</p> </p>
</div> </div>
</div> </div>
@@ -127,62 +129,59 @@ export default function AnalysisPage() {
</div> </div>
</div> </div>
{/* Usage Instructions */}
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg"> <div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
<span>🛠</span> <span>🛠</span>
<span>How to Use Web App</span> <span>{t("analysis.sect4_title")}</span>
</h2> </h2>
<div className="space-y-6"> <div className="space-y-6">
{/* Firefly GO Local */}
<div className="bg-white border border-purple-200 rounded-lg p-4"> <div className="bg-white border border-purple-200 rounded-lg p-4">
<h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2"> <h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2">
<span className="text-purple-600">🚀</span> <span className="text-purple-600">🚀</span>
<span>For Firefly GO Local</span> <span>{t("analysis.sect4_sub1")}</span>
</h3> </h3>
<div className="space-y-2 text-purple-700"> <div className="space-y-2 text-purple-700">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">1.</span> <span className="font-medium min-w-5 text-purple-600">1.</span>
<p>Launch the <span className="font-semibold">game</span> and your <span className="font-semibold">Firefly GO Server (PS)</span>.</p> <p>{t("analysis.sect4_sub1_step1_pre")}<span className="font-semibold">{t("analysis.sect4_sub1_step1_game")}</span>{t("analysis.sect4_sub1_step1_mid")}<span className="font-semibold">{t("analysis.sect4_sub1_step1_server")}</span>{t("analysis.sect4_sub1_step1_post")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">2.</span> <span className="font-medium min-w-5 text-purple-600">2.</span>
<p>Open one of the web analysis tools.</p> <p>{t("analysis.sect4_sub1_step2")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">3.</span> <span className="font-medium min-w-5 text-purple-600">3.</span>
<p>Go to <strong>Connection Settings</strong> select <strong>Connection Type: PS</strong> click <strong>Connect</strong>.</p> <p>{t("analysis.sect4_sub1_step3_pre")}<strong>{t("analysis.sect4_sub1_step3_conn")}</strong>{t("analysis.sect4_sub1_step3_mid1")}<strong>{t("analysis.sect4_sub1_step3_type")}</strong>{t("analysis.sect4_sub1_step3_mid2")}<strong>{t("analysis.sect4_sub1_step3_btn")}</strong>{t("analysis.sect4_sub1_step3_post")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">4.</span> <span className="font-medium min-w-5 text-purple-600">4.</span>
<p>Once connected, play the game. The tool will automatically analyze in the background.</p> <p>{t("analysis.sect4_sub1_step4")}</p>
</div> </div>
</div> </div>
</div> </div>
{/* Other Private Servers */}
<div className="bg-white border border-purple-200 rounded-lg p-4"> <div className="bg-white border border-purple-200 rounded-lg p-4">
<h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2"> <h3 className="font-semibold text-purple-800 mb-3 flex items-center gap-2">
<span className="text-purple-600">🌐</span> <span className="text-purple-600">🌐</span>
<span>For Other Private Servers</span> <span>{t("analysis.sect4_sub2")}</span>
</h3> </h3>
<div className="space-y-2 text-purple-700"> <div className="space-y-2 text-purple-700">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">1.</span> <span className="font-medium min-w-5 text-purple-600">1.</span>
<p>Launch the <span className="font-semibold">game</span> and your <span className="font-semibold">Private Server</span>.</p> <p>{t("analysis.sect4_sub2_step1_pre")}<span className="font-semibold">{t("analysis.sect4_sub2_step1_game")}</span>{t("analysis.sect4_sub2_step1_mid")}<span className="font-semibold">{t("analysis.sect4_sub2_step1_server")}</span>{t("analysis.sect4_sub2_step1_post")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">2.</span> <span className="font-medium min-w-5 text-purple-600">2.</span>
<p>Open one of the web analysis tools.</p> <p>{t("analysis.sect4_sub2_step2")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">3.</span> <span className="font-medium min-w-5 text-purple-600">3.</span>
<p>Go to <strong>Connection Settings</strong> select <strong>Connection Type: Native</strong> click <strong>Connect</strong>.</p> <p>{t("analysis.sect4_sub2_step3_pre")}<strong>{t("analysis.sect4_sub2_step3_conn")}</strong>{t("analysis.sect4_sub2_step3_mid1")}<strong>{t("analysis.sect4_sub2_step3_type")}</strong>{t("analysis.sect4_sub2_step3_mid2")}<strong>{t("analysis.sect4_sub2_step3_btn")}</strong>{t("analysis.sect4_sub2_step3_post")}</p>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-purple-600">4.</span> <span className="font-medium min-w-5 text-purple-600">4.</span>
<p>Once connected, play the game normally.</p> <p>{t("analysis.sect4_sub2_step4")}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -190,7 +189,7 @@ export default function AnalysisPage() {
</div> </div>
<div className="text-center pt-6"> <div className="text-center pt-6">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link> <Link to="/" className="btn btn-primary btn-wide">{t("analysis.btn_back")}</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,6 +6,7 @@ import { DiffService} from "@bindings/firefly-launcher/internal/diff-service"
import { FSService } from "@bindings/firefly-launcher/internal/fs-service" import { FSService } from "@bindings/firefly-launcher/internal/fs-service"
import { motion } from "motion/react" import { motion } from "motion/react"
import useDiffStore from "@/stores/diffStore" import useDiffStore from "@/stores/diffStore"
import { useTranslation } from "react-i18next"
export default function DiffPage() { export default function DiffPage() {
const { gameDir, setGameDir } = useSettingStore() const { gameDir, setGameDir } = useSettingStore()
@@ -29,6 +30,7 @@ export default function DiffPage() {
messageUpdate, messageUpdate,
setMessageUpdate setMessageUpdate
} = useDiffStore() } = useDiffStore()
const { t } = useTranslation()
useEffect(() => { useEffect(() => {
const getLanguage = async () => { const getLanguage = async () => {
@@ -61,15 +63,15 @@ export default function DiffPage() {
setFolderCheckResult(exists ? 'success' : 'error') setFolderCheckResult(exists ? 'success' : 'error')
setGameDir(exists ? basePath : '') setGameDir(exists ? basePath : '')
if (!exists) { if (!exists) {
toast.error('Game directory not found. Please select the correct folder.') toast.error(t("diff.toast_game_dir_not_found"))
} }
} else { } else {
toast.error('No folder path selected') toast.error(t("diff.toast_no_folder_selected"))
setFolderCheckResult('error') setFolderCheckResult('error')
setGameDir('') setGameDir('')
} }
} catch (err: any) { } catch (err: any) {
toast.error('PickFolder error:', err) toast.error(t("diff.toast_pick_folder_error"), err)
setFolderCheckResult('error') setFolderCheckResult('error')
} finally { } finally {
setIsLoading({ game: false, diff: false }) setIsLoading({ game: false, diff: false })
@@ -82,7 +84,7 @@ export default function DiffPage() {
const basePath = await FSService.PickFile("") const basePath = await FSService.PickFile("")
if (basePath) { if (basePath) {
if (!basePath.endsWith(".7z") && !basePath.endsWith(".zip") && !basePath.endsWith(".rar")) { if (!basePath.endsWith(".7z") && !basePath.endsWith(".zip") && !basePath.endsWith(".rar")) {
toast.error('Not valid file type') toast.error(t("diff.toast_invalid_file_type"))
setDiffCheckResult('error') setDiffCheckResult('error')
setDiffDir('') setDiffDir('')
return return
@@ -90,12 +92,12 @@ export default function DiffPage() {
setDiffDir(basePath) setDiffDir(basePath)
setDiffCheckResult('success') setDiffCheckResult('success')
} else { } else {
toast.error('No file path selected') toast.error(t("diff.toast_no_file_selected"))
setDiffCheckResult('error') setDiffCheckResult('error')
setDiffDir('') setDiffDir('')
} }
} catch (err: any) { } catch (err: any) {
toast.error('PickFile error:', err) toast.error(t("diff.toast_pick_file_error"), err)
setDiffCheckResult('error') setDiffCheckResult('error')
} finally { } finally {
setIsLoading({ game: false, diff: false }) setIsLoading({ game: false, diff: false })
@@ -115,11 +117,11 @@ export default function DiffPage() {
setIsDiffLoading(true) setIsDiffLoading(true)
if (!gameDir || !diffDir) { if (!gameDir || !diffDir) {
toast.error('Please select game directory and diff file') toast.error(t("diff.toast_select_both"))
return return
} }
setStageType('Check Type HDiff') setStageType(t("diff.stage_check_type"))
setProgressUpdate(0) setProgressUpdate(0)
setMaxProgressUpdate(1) setMaxProgressUpdate(1)
@@ -128,7 +130,7 @@ export default function DiffPage() {
setProgressUpdate(1) setProgressUpdate(1)
if (['hdiffmap.json', 'hdifffiles.txt', 'hdifffiles.json'].includes(validType)) { if (['hdiffmap.json', 'hdifffiles.txt', 'hdifffiles.json'].includes(validType)) {
setStageType('Version Validate') setStageType(t("diff.stage_version_validate"))
setProgressUpdate(0) setProgressUpdate(0)
setMaxProgressUpdate(1) setMaxProgressUpdate(1)
const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir) const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir)
@@ -136,11 +138,11 @@ export default function DiffPage() {
setProgressUpdate(1) setProgressUpdate(1)
} }
setStageType('Data Extract') setStageType(t("diff.stage_data_extract"))
const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir) const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir)
if (!handleResult(validData, errorData)) return if (!handleResult(validData, errorData)) return
setStageType('Cut Data') setStageType(t("diff.stage_cut_data"))
setMessageUpdate('') setMessageUpdate('')
const [validCut, errorCut] = await DiffService.CutData(gameDir) const [validCut, errorCut] = await DiffService.CutData(gameDir)
if (!handleResult(validCut, errorCut)) return if (!handleResult(validCut, errorCut)) return
@@ -149,24 +151,24 @@ export default function DiffPage() {
case 'hdifffiles.txt': case 'hdifffiles.txt':
case 'hdiffmap.json': case 'hdiffmap.json':
case 'hdifffiles.json': { case 'hdifffiles.json': {
setStageType('Patch Data') setStageType(t("diff.stage_patch_data"))
const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir) const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir)
if (!handleResult(validPatch, errorPatch)) return if (!handleResult(validPatch, errorPatch)) return
setStageType('Delete old files') setStageType(t("diff.stage_delete_old_files"))
const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir) const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir)
if (!handleResult(validDelete, errorDelete)) return if (!handleResult(validDelete, errorDelete)) return
break break
} }
case 'manifest': { case 'manifest': {
setStageType('Patch Data') setStageType(t("diff.stage_patch_data"))
const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir) const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir)
if (!handleResult(validPatch, errorPatch)) return if (!handleResult(validPatch, errorPatch)) return
break break
} }
} }
toast.success('Update game completed') toast.success(t("diff.toast_update_completed"))
} catch (err: any) { } catch (err: any) {
console.error(err) console.error(err)
toast.error(`PickFile error: ${err}`) toast.error(`PickFile error: ${err}`)
@@ -182,9 +184,9 @@ export default function DiffPage() {
{/* Header */} {/* Header */}
<div className="text-center mb-2"> <div className="text-center mb-2">
<h1 className="text-4xl font-bold mb-2"> <h1 className="text-4xl font-bold mb-2">
🎮 Game Update by Hdiffz {t("diff.header_title")}
</h1> </h1>
<p className="">Help you update game with hdiffz</p> <p className="">{t("diff.header_desc")}</p>
</div> </div>
{/* Main Content */} {/* Main Content */}
@@ -194,7 +196,7 @@ export default function DiffPage() {
<div className="pb-2"> <div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2"> <h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<Folder className="text-primary" size={24} /> <Folder className="text-primary" size={24} />
Game Directory {t("diff.game_dir_title")}
</h2> </h2>
<div className="space-y-1"> <div className="space-y-1">
@@ -205,7 +207,7 @@ export default function DiffPage() {
className="btn btn-primary" className="btn btn-primary"
> >
<Folder size={20} /> <Folder size={20} />
{isLoading.game ? 'Selecting...' : 'Select Game Folder'} {isLoading.game ? t("diff.btn_selecting") : t("diff.btn_select_game")}
</button> </button>
{gameDir && ( {gameDir && (
@@ -224,12 +226,12 @@ export default function DiffPage() {
{folderCheckResult === 'success' ? ( {folderCheckResult === 'success' ? (
<> <>
<Check size={20} /> <Check size={20} />
<span>Valid game directory found!</span> <span>{t("diff.game_dir_valid")}</span>
</> </>
) : ( ) : (
<> <>
<X size={20} /> <X size={20} />
<span>Game directory not found. Please select the correct folder.</span> <span>{t("diff.game_dir_invalid")}</span>
</> </>
)} )}
</div> </div>
@@ -241,7 +243,7 @@ export default function DiffPage() {
<div className="pb-2"> <div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2"> <h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<File className="text-primary" size={24} /> <File className="text-primary" size={24} />
Diff file Directory {t("diff.diff_file_title")}
</h2> </h2>
<div className="space-y-1"> <div className="space-y-1">
@@ -252,7 +254,7 @@ export default function DiffPage() {
className="btn btn-primary" className="btn btn-primary"
> >
<File size={20} /> <File size={20} />
{isLoading.diff ? 'Selecting...' : 'Select Diff file Folder'} {isLoading.diff ? t("diff.btn_selecting") : t("diff.btn_select_diff")}
</button> </button>
{diffDir && ( {diffDir && (
@@ -271,12 +273,12 @@ export default function DiffPage() {
{diffCheckResult === 'success' ? ( {diffCheckResult === 'success' ? (
<> <>
<Check size={20} /> <Check size={20} />
<span>Valid diff file found!</span> <span>{t("diff.diff_file_valid")}</span>
</> </>
) : ( ) : (
<> <>
<X size={20} /> <X size={20} />
<span>Diff file not found. Please select the correct file.</span> <span>{t("diff.diff_file_invalid")}</span>
</> </>
)} )}
</div> </div>
@@ -288,10 +290,10 @@ export default function DiffPage() {
<button <button
onClick={handleUpdateGame} onClick={handleUpdateGame}
disabled={!diffDir || !gameDir || isLoading.game || isLoading.diff} disabled={!diffDir || !gameDir || isLoading.game || isLoading.diff}
className="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 text-white px-8 py-3 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl disabled:cursor-not-allowed cursor-pointer" className="bg-linear-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 text-white px-8 py-3 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl disabled:cursor-not-allowed cursor-pointer"
> >
<Settings size={20} /> <Settings size={20} />
{isDiffLoading ? 'Updating...' : 'Update Game'} {isDiffLoading ? t("diff.btn_updating") : t("diff.btn_update_game") }
</button> </button>
</div> </div>
</div> </div>
@@ -300,8 +302,8 @@ export default function DiffPage() {
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20"> <div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
<div className="border-b border-purple-500/30 px-6 py-4 mb-4 text-center"> <div className="border-b border-purple-500/30 px-6 py-4 mb-4 text-center">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400"> <h3 className="font-bold text-2xl text-transparent bg-clip-text bg-linear-to-r from-pink-400 to-cyan-400">
Update Game {t("diff.modal_update_title")}
</h3> </h3>
</div> </div>
@@ -317,14 +319,14 @@ export default function DiffPage() {
</div> </div>
<div className="w-full bg-white/20 rounded-full h-2 overflow-hidden"> <div className="w-full bg-white/20 rounded-full h-2 overflow-hidden">
<motion.div <motion.div
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full" className="h-full bg-linear-to-r from-cyan-400 to-blue-500 rounded-full"
initial={{ width: 0 }} initial={{ width: 0 }}
animate={{ width: `${(progressUpdate / maxProgressUpdate) * 100}%` }} animate={{ width: `${(progressUpdate / maxProgressUpdate) * 100}%` }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
/> />
</div> </div>
<div className="text-center text-lg text-white/60"> <div className="text-center text-lg text-white/60">
Please wait... {t("diff.status_wait")}
</div> </div>
</div> </div>
</div> </div>
@@ -336,13 +338,13 @@ export default function DiffPage() {
{/* Instructions */} {/* Instructions */}
<div className="bg-info/5 rounded-lg p-4 border border-info/30 mt-6"> <div className="bg-info/5 rounded-lg p-4 border border-info/30 mt-6">
<h3 className="font-medium text-error mb-2">📋 Instructions:</h3> <h3 className="font-medium text-error mb-2">{t("diff.inst_title")}</h3>
<ol className="text-sm text-error space-y-1"> <ol className="text-sm text-error space-y-1">
<li>1. Click "Select Game Folder" and choose your game's root directory</li> <li>{t("diff.inst_step_1")}</li>
<li>2. Wait for the system to validate the game directory</li> <li>{t("diff.inst_step_2")}</li>
<li>3. Click "Select Diff file Folder" and choose your diff file's root directory</li> <li>{t("diff.inst_step_3")}</li>
<li>4. Wait for the system to validate the diff file directory</li> <li>{t("diff.inst_step_4")}</li>
<li>5. Click "Update Game" to save your changes</li> <li>{t("diff.inst_step_5")}</li>
</ol> </ol>
</div> </div>
</div> </div>

View File

@@ -1,31 +1,33 @@
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
export default function FireflyToolsPage() { export default function FireflyToolsPage() {
const { t } = useTranslation();
return ( return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6"> <div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8"> <div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1> <h1 className="text-4xl font-bold text-primary text-center">{t("fireflytools.title")}</h1>
{/* Section 1: About SR Tools */}
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg"> <div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
<span></span> <span></span>
<span>About Firefly Tools</span> <span>{t("fireflytools.sect1_title")}</span>
</h2> </h2>
<div className="space-y-3 text-blue-700"> <div className="space-y-3 text-blue-700">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🏠</div> <div className="text-blue-600 text-lg">🏠</div>
<p> <p>
This site is a another version of {" "} {t("fireflytools.sect1_p1_pre")}
<span className="font-semibold text-success">Firefly Tools {" "}</span> <span className="font-semibold text-success">{t("fireflytools.sect1_p1_tool")}</span>
developed by {" "} {t("fireflytools.sect1_p1_mid")}
<span className="font-semibold text-accent">Firefly Shelter</span> <span className="font-semibold text-accent">{t("fireflytools.sect1_p1_author")}</span>
</p> </p>
</div> </div>
<div className="bg-white border border-blue-200 rounded-lg p-4"> <div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-blue-600 text-lg">🏆</span> <span className="text-blue-600 text-lg">🏆</span>
<span className="font-semibold text-blue-800">Master Website</span> <span className="font-semibold text-blue-800">{t("fireflytools.sect1_master_site")}</span>
</div> </div>
<a <a
href="https://srtools.punklorde.org" href="https://srtools.punklorde.org"
@@ -38,11 +40,11 @@ export default function FireflyToolsPage() {
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">👨💻</div> <div className="text-blue-600 text-lg">👨💻</div>
<p>The original tool was created by a third-party developer named <span className="font-semibold text-accent">Amazing</span>. This version is directly based on that work, without modification to core logic.</p> <p>{t("fireflytools.sect1_p2_pre")}<span className="font-semibold text-accent">{t("fireflytools.sect1_p2_author")}</span>{t("fireflytools.sect1_p2_post")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔗</div> <div className="text-blue-600 text-lg">🔗</div>
<p>There is also a more modern version by the same author available at{" "} <p>{t("fireflytools.sect1_p3_pre")}
<a <a
href="https://srtools.neonteam.dev" href="https://srtools.neonteam.dev"
className="link link-accent" className="link link-accent"
@@ -56,75 +58,73 @@ export default function FireflyToolsPage() {
</div> </div>
</div> </div>
{/* Section 2: Main Features */}
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg"> <div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
<span>🔧</span> <span>🔧</span>
<span>Main Features</span> <span>{t("fireflytools.sect2_title")}</span>
</h2> </h2>
<div className="space-y-3 text-green-700"> <div className="space-y-3 text-green-700">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div> <div className="text-green-600 text-lg"></div>
<p>Configure characters, light cones, relics, traces, and eidolons easily in your browser.</p> <p>{t("fireflytools.sect2_feat1")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🔌</div> <div className="text-green-600 text-lg">🔌</div>
<p>Instantly apply setups to <span className="font-semibold text-accent">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> no manual file uploads required.</p> <p>{t("fireflytools.sect2_feat2_pre")}<span className="font-semibold text-accent">{t("fireflytools.sect2_feat2_server")}</span>{t("fireflytools.sect2_feat2_mid")}<span className="font-semibold">{t("fireflytools.sect2_feat2_conn")}</span>{t("fireflytools.sect2_feat2_post")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-2xl"></div> <div className="text-green-600 text-2xl"></div>
<div> <div>
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4> <h4 className="font-semibold text-green-800 text-lg">{t("fireflytools.sect2_feat3_title")}</h4>
<p className="text-green-700 mt-1"> <p className="text-green-700 mt-1">
Enhance your <span className="font-semibold text-accent">Firefly GO Server</span> experience with extra features: {t("fireflytools.sect2_feat3_desc_pre")}<span className="font-semibold text-accent">{t("fireflytools.sect2_feat3_desc_server")}</span>{t("fireflytools.sect2_feat3_desc_post")}
</p> </p>
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700"> <ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
<li>🎭 <span className="font-medium">Hidden Game UI</span> remove the entire game interface.</li> <li>🎭 <span className="font-medium">{t("fireflytools.sect2_feat3_ui")}</span> {t("fireflytools.sect2_feat3_ui_desc")}</li>
<li>🚫 <span className="font-medium">Disable Censorship</span> get rid of Lens Flare censor 💀.</li> <li>🚫 <span className="font-medium">{t("fireflytools.sect2_feat3_censor")}</span> {t("fireflytools.sect2_feat3_censor_desc")}</li>
<li>🧪 <span className="font-medium">Theorycraft Mode</span> configure HP, cycles, and more via the web.</li> <li>🧪 <span className="font-medium">{t("fireflytools.sect2_feat3_tc")}</span> {t("fireflytools.sect2_feat3_tc_desc")}</li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg">📂</div> <div className="text-green-600 text-lg">📂</div>
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p> <p>{t("fireflytools.sect2_feat4_pre")}<code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>{t("fireflytools.sect2_feat4_post")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg"></div> <div className="text-green-600 text-lg"></div>
<p>Fast testing workflow no sync cooldowns, instant in-game updates.</p> <p>{t("fireflytools.sect2_feat5")}</p>
</div> </div>
</div> </div>
</div> </div>
{/* Section 3: Getting Started */}
<div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg"> <div className="bg-purple-50 border-l-4 border-purple-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-purple-800 flex items-center gap-2 mb-4">
<span>🚀</span> <span>🚀</span>
<span>Getting Started</span> <span>{t("fireflytools.sect3_title")}</span>
</h2> </h2>
<div className="space-y-3 text-purple-700"> <div className="space-y-3 text-purple-700">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">1</div> <div className="text-purple-600 text-lg">1</div>
<p>Access the tool through your browser at the self-hosted instance.</p> <p>{t("fireflytools.sect3_step1")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">2</div> <div className="text-purple-600 text-lg">2</div>
<p>Configure your character builds with the intuitive web interface.</p> <p>{t("fireflytools.sect3_step2")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">3</div> <div className="text-purple-600 text-lg">3</div>
<p>Use <span className="font-semibold">Connect PS</span> feature to instantly sync with your private server.</p> <p>{t("fireflytools.sect3_step3_pre")}<span className="font-semibold">{t("fireflytools.sect3_step3_bold")}</span>{t("fireflytools.sect3_step3_post")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-purple-600 text-lg">4</div> <div className="text-purple-600 text-lg">4</div>
<p>Test your builds in-game with real-time updates and modifications.</p> <p>{t("fireflytools.sect3_step4")}</p>
</div> </div>
</div> </div>
</div> </div>
<div className="text-center pt-4"> <div className="text-center pt-4">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link> <Link to="/" className="btn btn-primary btn-wide">{t("fireflytools.btn_back")}</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,49 +1,48 @@
import { Link } from '@tanstack/react-router'; import { Link } from '@tanstack/react-router';
import { useTranslation } from 'react-i18next';
export default function HowToPage() { export default function HowToPage() {
const { t } = useTranslation();
return ( return (
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6"> <div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
<div className=" w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8"> <div className=" w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
<h1 className="text-4xl font-bold text-primary text-center">How to Use</h1> <h1 className="text-4xl font-bold text-primary text-center">{t("howto.title")}</h1>
{/* Section 1: Launcher Features */} {/* Section 1: Launcher Features */}
<div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg"> <div className="bg-green-50 border-l-4 border-green-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-green-800 flex items-center gap-2 mb-4">
<span>🚀</span> <span>🚀</span>
<span>Using the Launcher Features</span> <span>{t("howto.sect1_title")}</span>
</h2> </h2>
<div className="space-y-3 text-green-700"> <div className="space-y-3 text-green-700">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🔄</div> <div className="text-green-600 text-lg">🔄</div>
<p>Automatically update <span className="font-semibold text-amber-600">Firefly Go</span> and proxy tools when launching.</p> <p>{t("howto.sect1_auto_update_pre")} <span className="font-semibold text-amber-600">Firefly Go</span> {t("howto.sect1_auto_update_post")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🎮</div> <div className="text-green-600 text-lg">🎮</div>
<p>Launch the game directly through the launcher with correct parameters and runtime environment.</p> <p>{t("howto.sect1_launch_game")}</p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-lg">🌐</div> <div className="text-green-600 text-lg">🌐</div>
<p>Support switching in-game language (e.g., EN, JP, ZH, KR) via{" "} <p>{t("howto.sect1_lang_pre")} <Link to="/language" className="link link-info font-mono">{t("howto.sect1_lang_link")}</Link>
<Link to="/language" className="link link-info font-mono">Language Tools</Link>
</p> </p>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-2xl">📦</div> <div className="text-green-600 text-2xl">📦</div>
<div> <div>
<p className="text-green-800 font-semibold"> <p className="text-green-800 font-semibold">
Patch & Update Game Files {t("howto.sect1_patch_title")}
</p> </p>
<p className="text-green-700"> <p className="text-green-700">
Use the{" "} {t("howto.sect1_patch_desc_1_pre")} <Link to="/diff" className="link link-info font-mono">{t("howto.sect1_patch_desc_1_link")}</Link> {t("howto.sect1_patch_desc_1_post")}
<Link to="/diff" className="link link-info font-mono">Diff Tool</Link>{" "}
(<span className="font-medium">DiffPatch</span>) for fast & lightweight incremental updates.
</p> </p>
<p className="text-green-700 mt-1"> <p className="text-green-700 mt-1">
Supports <span className="font-semibold">Hdiff</span>, <span className="font-semibold">Ldiff</span>, and custom diff formats. {t("howto.sect1_patch_desc_2_pre")} <span className="font-semibold">Hdiff</span>, <span className="font-semibold">Ldiff</span>{t("howto.sect1_patch_desc_2_post")}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -51,12 +50,11 @@ export default function HowToPage() {
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg"> <div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
<span>📜</span> <span>📜</span>
<span>FireflyGo Chat Commands</span> <span>{t("howto.sect2_title")}</span>
</h2> </h2>
<p className="text-blue-700 mb-4"> <p className="text-blue-700 mb-4">
Below are in-game chat commands you can use. Some commands require you to enable{" "} {t("howto.sect2_desc_pre")} <span className="font-semibold text-accent">{t("howto.sect2_desc_bold")}</span>{t("howto.sect2_desc_post")}
<span className="font-semibold text-accent">Theorycraft Mode</span>.
</p> </p>
{/* Theorycraft Mode Warning */} {/* Theorycraft Mode Warning */}
@@ -64,8 +62,8 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-red-600 text-xl">🔒</div> <div className="text-red-600 text-xl">🔒</div>
<div> <div>
<h3 className="font-semibold text-red-800 mb-2">Theorycraft Mode Required</h3> <h3 className="font-semibold text-red-800 mb-2">{t("howto.sect2_tc_req_title")}</h3>
<p className="text-red-700 mb-2">The following commands are only available when <strong>Theorycraft Mode</strong> is enabled:</p> <p className="text-red-700 mb-2">{t("howto.sect2_tc_req_desc_pre")} <strong>{t("howto.sect2_tc_req_desc_bold")}</strong> {t("howto.sect2_tc_req_desc_post")}</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<code className="bg-red-100 px-2 py-1 rounded text-sm text-red-800">/cycle</code> <code className="bg-red-100 px-2 py-1 rounded text-sm text-red-800">/cycle</code>
<code className="bg-red-100 px-2 py-1 rounded text-sm text-red-800">/hp</code> <code className="bg-red-100 px-2 py-1 rounded text-sm text-red-800">/hp</code>
@@ -79,36 +77,34 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg"></div> <div className="text-blue-600 text-lg"></div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Extra Settings</h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_extra_title")}</h4>
<div className="space-y-4 text-blue-700 text-sm"> <div className="space-y-4 text-blue-700 text-sm">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3"> <div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<h5 className="font-semibold text-blue-800 flex items-center gap-2"> <h5 className="font-semibold text-blue-800 flex items-center gap-2">
🎭 Hidden UI 🎭 {t("howto.sect2_hidden_ui_title")}
</h5> </h5>
<p className="mt-1"> <p className="mt-1">
Instantly hides the entire game UI often used in DIM showcase videos. {t("howto.sect2_hidden_ui_desc")}
</p>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<h5 className="font-semibold text-blue-800 flex items-center gap-2">
🚫 Disable Censorship
</h5>
<p className="mt-1">
Remove the Lens Flare censor effect 💀 for a cleaner experience.
</p> </p>
</div> </div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3"> <div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<h5 className="font-semibold text-blue-800 flex items-center gap-2"> <h5 className="font-semibold text-blue-800 flex items-center gap-2">
🧪 Theorycraft Mode 🚫 {t("howto.sect2_censor_title")}
</h5> </h5>
<p className="mt-1"> <p className="mt-1">
No need to type chat commands anymore configure everything through the {t("howto.sect2_censor_desc")}
web: adjust monster HP, set cycles, view logs, and more. </p>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<h5 className="font-semibold text-blue-800 flex items-center gap-2">
🧪 {t("howto.sect2_tc_title")}
</h5>
<p className="mt-1">
{t("howto.sect2_tc_desc")}
</p> </p>
</div> </div>
</div> </div>
@@ -120,7 +116,7 @@ export default function HowToPage() {
title="Extra Settings Tutorial" title="Extra Settings Tutorial"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen allowFullScreen
className="rounded-lg w-full h-[300px]" className="rounded-lg w-full h-75"
></iframe> ></iframe>
</div> </div>
</div> </div>
@@ -129,17 +125,17 @@ export default function HowToPage() {
{/* Commands List */} {/* Commands List */}
<div className="space-y-4 mt-4"> <div className="space-y-4 mt-4">
<h3 className="text-lg font-semibold text-blue-800">Available Commands:</h3> <h3 className="text-lg font-semibold text-blue-800">{t("howto.sect2_cmd_title")}</h3>
{/* Theorycraft Toggle */} {/* Theorycraft Toggle */}
<div className="bg-white border border-blue-200 rounded-lg p-4"> <div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg"></div> <div className="text-blue-600 text-lg"></div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Theorycraft Mode</h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_tc")}</h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/theorycraft 1</code> Enable Theorycraft Mode</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/theorycraft 1</code> {t("howto.sect2_cmd_tc_enable")}</p>
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/theorycraft 0</code> Disable Theorycraft Mode</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/theorycraft 0</code> {t("howto.sect2_cmd_tc_disable")}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -150,11 +146,11 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔄</div> <div className="text-blue-600 text-lg">🔄</div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Cycle Control <span className="text-red-600 text-sm">(Theorycraft only)</span></h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_cycle")} <span className="text-red-600 text-sm">{t("howto.sect2_cmd_tc_only")}</span></h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle N</code> Set cycle count in battle</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle N</code> {t("howto.sect2_cmd_cycle_desc")}</p>
<p className="text-sm">Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle 30</code> sets battle to 30 cycles</p> <p className="text-sm">{t("howto.sect2_cmd_cycle_ex1_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle 30</code> {t("howto.sect2_cmd_cycle_ex1_post")}</p>
<p className="text-sm"><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle 0</code> disables custom cycle</p> <p className="text-sm"><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/cycle 0</code> {t("howto.sect2_cmd_cycle_ex2_post")}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -166,37 +162,36 @@ export default function HowToPage() {
<div className="text-blue-600 text-lg"></div> <div className="text-blue-600 text-lg"></div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1"> <h4 className="font-semibold text-blue-800 mb-1">
HP Override <span className="text-red-600 text-sm">(Theorycraft only)</span> {t("howto.sect2_cmd_hp")} <span className="text-red-600 text-sm">{t("howto.sect2_cmd_tc_only")}</span>
</h4> </h4>
<div className="space-y-2 text-blue-700 text-sm"> <div className="space-y-2 text-blue-700 text-sm">
<p> <p>
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp N</code> Set monster HP (only available in Theorycraft mode) <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp N</code> {t("howto.sect2_cmd_hp_desc1")}
</p> </p>
<p> <p>
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 0</code> Disable the set HP feature <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 0</code> {t("howto.sect2_cmd_hp_desc2")}
</p> </p>
<p> <p>
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp Wave V1 V2 ...</code> Set HP for each monster in a specific wave <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp Wave V1 V2 ...</code> {t("howto.sect2_cmd_hp_desc3")}
</p> </p>
<p className="ml-4"> <p className="ml-4">
Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 1 2000000 3000000</code> sets wave 1 monster1 HP=2,000,000 and monster2 HP=3,000,000 {t("howto.sect2_cmd_hp_ex_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 1 2000000 3000000</code> {t("howto.sect2_cmd_hp_ex_post")}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Log Command */} {/* Log Command */}
<div className="bg-white border border-blue-200 rounded-lg p-4"> <div className="bg-white border border-blue-200 rounded-lg p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">📝</div> <div className="text-blue-600 text-lg">📝</div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Battle Log <span className="text-red-600 text-sm">(Theorycraft only)</span></h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_log")} <span className="text-red-600 text-sm">{t("howto.sect2_cmd_tc_only")}</span></h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/log 1</code> Enable battle log output</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/log 1</code> {t("howto.sect2_cmd_log_desc1")}</p>
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/log 0</code> Disable battle log</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/log 0</code> {t("howto.sect2_cmd_log_desc2")}</p>
<p className="text-sm">Output will be written as <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">.json</code></p> <p className="text-sm">{t("howto.sect2_cmd_log_out_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">.json</code></p>
</div> </div>
</div> </div>
</div> </div>
@@ -207,11 +202,11 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg"></div> <div className="text-blue-600 text-lg"></div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Skip Nodes</h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_skip")}</h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip N</code> Skip nodes in MOC / AS / Pure Fiction</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip N</code> {t("howto.sect2_cmd_skip_desc")}</p>
<p className="text-sm">Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip 2</code> skips node 2</p> <p className="text-sm">{t("howto.sect2_cmd_skip_ex1_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip 2</code> {t("howto.sect2_cmd_skip_ex1_post")}</p>
<p className="text-sm"><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip 0</code> disables skipping</p> <p className="text-sm"><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/skip 0</code> {t("howto.sect2_cmd_skip_ex2_post")}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -222,11 +217,11 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔄</div> <div className="text-blue-600 text-lg">🔄</div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Character Path Switch</h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_id")}</h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/id CHAR_ID</code> Switch path for multi-form characters</p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/id CHAR_ID</code> {t("howto.sect2_cmd_id_desc")}</p>
<p className="text-sm">Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/id 8008</code> to change MC (Trailblazer) form</p> <p className="text-sm">{t("howto.sect2_cmd_id_ex1_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/id 8008</code> {t("howto.sect2_cmd_id_ex1_post")}</p>
<p className="text-sm">Works with IDs like <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">8001 8008</code>, <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">1001 1224</code></p> <p className="text-sm">{t("howto.sect2_cmd_id_ex2_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">8001 8008</code>, <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">1001 1224</code></p>
</div> </div>
</div> </div>
</div> </div>
@@ -237,9 +232,9 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-lg">🔄</div> <div className="text-blue-600 text-lg">🔄</div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-semibold text-blue-800 mb-1">Refresh Data</h4> <h4 className="font-semibold text-blue-800 mb-1">{t("howto.sect2_cmd_update")}</h4>
<div className="space-y-1 text-blue-700"> <div className="space-y-1 text-blue-700">
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/update</code> Refresh server data from current <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">freesr-data.json</code></p> <p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/update</code> {t("howto.sect2_cmd_update_desc_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">freesr-data.json</code></p>
</div> </div>
</div> </div>
</div> </div>
@@ -251,7 +246,7 @@ export default function HowToPage() {
<div className="bg-gray-50 border-l-4 border-gray-400 p-6 rounded-r-lg"> <div className="bg-gray-50 border-l-4 border-gray-400 p-6 rounded-r-lg">
<h2 className="text-2xl font-bold text-gray-800 flex items-center gap-2 mb-4"> <h2 className="text-2xl font-bold text-gray-800 flex items-center gap-2 mb-4">
<span>📌</span> <span>📌</span>
<span>Other Notes</span> <span>{t("howto.sect3_title")}</span>
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
@@ -260,9 +255,9 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-yellow-600 text-xl"></div> <div className="text-yellow-600 text-xl"></div>
<div> <div>
<h3 className="font-semibold text-yellow-800 mb-1">Administrator Rights</h3> <h3 className="font-semibold text-yellow-800 mb-1">{t("howto.sect3_admin_title")}</h3>
<p className="text-yellow-700"> <p className="text-yellow-700">
Always run the launcher as Administrator for file permission access. {t("howto.sect3_admin_desc")}
</p> </p>
</div> </div>
</div> </div>
@@ -273,10 +268,9 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-blue-600 text-xl">💾</div> <div className="text-blue-600 text-xl">💾</div>
<div> <div>
<h3 className="font-semibold text-blue-800 mb-1">Backup Data</h3> <h3 className="font-semibold text-blue-800 mb-1">{t("howto.sect3_backup_title")}</h3>
<p className="text-blue-700"> <p className="text-blue-700">
Backup your <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">config.json</code> and{' '} {t("howto.sect3_backup_desc_pre")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">config.json</code> {t("howto.sect3_backup_desc_mid")} <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">freesr-data.json</code> {t("howto.sect3_backup_desc_post")}
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">freesr-data.json</code> regularly.
</p> </p>
</div> </div>
</div> </div>
@@ -287,21 +281,21 @@ export default function HowToPage() {
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="text-green-600 text-xl">🎵</div> <div className="text-green-600 text-xl">🎵</div>
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold text-green-800 mb-2">Enable Voice Packs in Beta Client</h3> <h3 className="font-semibold text-green-800 mb-2">{t("howto.sect3_voice_title")}</h3>
<div className="space-y-3 text-green-700"> <div className="space-y-3 text-green-700">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-green-600">1.</span> <span className="font-medium min-w-5 text-green-600">1.</span>
<div> <div>
<p className="mb-1">Copy the desired voice folder (e.g., <code className="bg-green-100 px-1 py-0.5 rounded text-sm">Japanese</code>, <code className="bg-green-100 px-1 py-0.5 rounded text-sm">English</code>) from:</p> <p className="mb-1">{t("howto.sect3_voice_step1_pre")} <code className="bg-green-100 px-1 py-0.5 rounded text-sm">Japanese</code>, <code className="bg-green-100 px-1 py-0.5 rounded text-sm">English</code>{t("howto.sect3_voice_step1_mid")}</p>
<code className="block bg-green-100 px-2 py-1 rounded text-sm mt-1"> <code className="block bg-green-100 px-2 py-1 rounded text-sm mt-1">
Star Rail\Games\StarRail_Data\Persistent\Audio\AudioPackage\Windows Star Rail\Games\StarRail_Data\Persistent\Audio\AudioPackage\Windows
</code> </code>
<p className="mt-1">to the beta folder by clicking <strong>"Open Voice Folder"</strong> on the Home tab.</p> <p className="mt-1">{t("howto.sect3_voice_step1_post_pre")} <strong>{t("howto.sect3_voice_step1_post_bold")}</strong> {t("howto.sect3_voice_step1_post_post")}</p>
</div> </div>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="font-medium min-w-[20px] text-green-600">2.</span> <span className="font-medium min-w-5 text-green-600">2.</span>
<p>When launching the game for the first time, it may delete the voice folder. If so, repeat step 1 to restore it.</p> <p>{t("howto.sect3_voice_step2")}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -311,7 +305,7 @@ export default function HowToPage() {
</div> </div>
<div className="text-center pt-4"> <div className="text-center pt-4">
<Link to="/" className="btn btn-primary btn-wide">Back to Home</Link> <Link to="/" className="btn btn-primary btn-wide">{t("howto.btn_back")}</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,6 +4,7 @@ import { FSService } from '@bindings/firefly-launcher/internal/fs-service'
import { LanguageService } from '@bindings/firefly-launcher/internal/language-service' import { LanguageService } from '@bindings/firefly-launcher/internal/language-service'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useSettingStore from '@/stores/settingStore' import useSettingStore from '@/stores/settingStore'
import { useTranslation } from "react-i18next"
export default function LanguagePage() { export default function LanguagePage() {
const { gameDir, setGameDir } = useSettingStore() const { gameDir, setGameDir } = useSettingStore()
@@ -24,6 +25,7 @@ export default function LanguagePage() {
{ value: 'jp', label: 'Japanese', flag: '🇯🇵' }, { value: 'jp', label: 'Japanese', flag: '🇯🇵' },
{ value: 'kr', label: 'Korean', flag: '🇰🇷' } { value: 'kr', label: 'Korean', flag: '🇰🇷' }
] ]
const { t } = useTranslation()
useEffect(() => { useEffect(() => {
const getLanguage = async () => { const getLanguage = async () => {
@@ -78,15 +80,15 @@ export default function LanguagePage() {
setFolderCheckResult(exists ? 'success' : 'error') setFolderCheckResult(exists ? 'success' : 'error')
setGameDir(exists ? basePath : "") setGameDir(exists ? basePath : "")
if (!exists) { if (!exists) {
toast.error('Game directory not found. Please select the correct folder.') toast.error(t("diff.toast_game_dir_not_found"))
} }
} else { } else {
toast.error('No folder path selected') toast.error(t("language.toast_no_folder_selected"))
setFolderCheckResult('error') setFolderCheckResult('error')
setGameDir('') setGameDir('')
} }
} catch (err: any) { } catch (err: any) {
toast.error('PickFolder error:', err) toast.error(t("language.toast_pick_folder_error"), err)
setFolderCheckResult('error') setFolderCheckResult('error')
} finally { } finally {
setIsLoading(false) setIsLoading(false)
@@ -95,7 +97,7 @@ export default function LanguagePage() {
const handleSetLanguage = async () => { const handleSetLanguage = async () => {
if (!gameDir) { if (!gameDir) {
toast.error('No folder path selected') toast.error(t("language.toast_no_folder_selected"))
return return
} }
try { try {
@@ -106,7 +108,7 @@ export default function LanguagePage() {
selectedVoiceLang selectedVoiceLang
) )
if (ok) { if (ok) {
toast.success('Language set successfully') toast.success(t("language.toast_set_language_success"))
setTextLang(selectedTextLang) setTextLang(selectedTextLang)
setVoiceLang(selectedVoiceLang) setVoiceLang(selectedVoiceLang)
} }
@@ -115,7 +117,7 @@ export default function LanguagePage() {
} }
} catch (err: any) { } catch (err: any) {
toast.error('SetLanguage error:', err) toast.error(t("language.toast_set_language_error"), err)
} finally { } finally {
setIsSettingLanguage(false) setIsSettingLanguage(false)
} }
@@ -132,9 +134,9 @@ export default function LanguagePage() {
{/* Header */} {/* Header */}
<div className="text-center mb-2"> <div className="text-center mb-2">
<h1 className="text-4xl font-bold mb-2"> <h1 className="text-4xl font-bold mb-2">
🎮 Game Language Manager {t("language.header_title")}
</h1> </h1>
<p className="">Manage text and voice language settings for your game</p> <p className="">{t("language.header_desc")}</p>
</div> </div>
{/* Main Content */} {/* Main Content */}
@@ -144,7 +146,7 @@ export default function LanguagePage() {
<div className="pb-2"> <div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2"> <h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<Folder className="text-primary" size={24} /> <Folder className="text-primary" size={24} />
Game Directory {t("language.game_dir_title")}
</h2> </h2>
<div className="space-y-1"> <div className="space-y-1">
@@ -155,7 +157,7 @@ export default function LanguagePage() {
className="btn btn-primary" className="btn btn-primary"
> >
<Folder size={20} /> <Folder size={20} />
{isLoading ? 'Selecting...' : 'Select Game Folder'} {isLoading ? t("language.btn_selecting") : t("language.btn_select_game") }
</button> </button>
{gameDir && ( {gameDir && (
@@ -174,12 +176,12 @@ export default function LanguagePage() {
{folderCheckResult === 'success' ? ( {folderCheckResult === 'success' ? (
<> <>
<Check size={20} /> <Check size={20} />
<span>Valid game directory found!</span> <span>{t("language.game_dir_valid")}</span>
</> </>
) : ( ) : (
<> <>
<X size={20} /> <X size={20} />
<span>Game directory not found. Please select the correct folder.</span> <span>{t("language.game_dir_invalid")}</span>
</> </>
)} )}
</div> </div>
@@ -192,14 +194,14 @@ export default function LanguagePage() {
<div className="pb-2"> <div className="pb-2">
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2"> <h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<Globe className="text-primary" size={24} /> <Globe className="text-primary" size={24} />
Current Languages {t("language.current_languages_title")}
</h2> </h2>
<div className="grid md:grid-cols-2 gap-4"> <div className="grid md:grid-cols-2 gap-4">
<div className="bg-success/5 rounded-lg p-2 border border-success/30"> <div className="bg-success/5 rounded-lg p-2 border border-success/30">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Globe size={20} className="text-success" /> <Globe size={20} className="text-success" />
<span className="font-bold text-success">Text Language</span> <span className="font-bold text-success">{t("language.text_language")}</span>
</div> </div>
<p className="text-2xl font-bold text-success"> <p className="text-2xl font-bold text-success">
{getLanguageLabel(textLang)} {getLanguageLabel(textLang)}
@@ -209,7 +211,7 @@ export default function LanguagePage() {
<div className="bg-warning/5 rounded-lg p-2 border border-warning/30"> <div className="bg-warning/5 rounded-lg p-2 border border-warning/30">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Mic size={20} className="text-accent" /> <Mic size={20} className="text-accent" />
<span className="font-bold text-accent">Voice Language</span> <span className="font-bold text-accent">{t("language.voice_language")}</span>
</div> </div>
<p className="text-2xl font-bold text-accent"> <p className="text-2xl font-bold text-accent">
{getLanguageLabel(voiceLang)} {getLanguageLabel(voiceLang)}
@@ -224,22 +226,21 @@ export default function LanguagePage() {
}`}> }`}>
<h2 className="text-2xl font-semibold mb-4 flex items-center gap-2"> <h2 className="text-2xl font-semibold mb-4 flex items-center gap-2">
<Settings className="text-primary" size={24} /> <Settings className="text-primary" size={24} />
Language Settings {t("language.language_settings_title")}
</h2> </h2>
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
{/* Text Language */}
<div className="space-y-3"> <div className="space-y-3">
<label className="flex text-sm font-medium text-success items-center gap-2"> <label className="flex text-sm font-medium text-success items-center gap-2">
<Globe size={16} /> <Globe size={16} />
Text Language {t("language.text_language")}
</label> </label>
<select <select
value={selectedTextLang} value={selectedTextLang}
onChange={(e) => setSelectedTextLang(e.target.value)} onChange={(e) => setSelectedTextLang(e.target.value)}
className="w-full select select-success" className="w-full select select-success"
> >
<option value="">Select text language...</option> <option value="">{t("language.select_text_placeholder")}</option>
{languageOptions.map(lang => ( {languageOptions.map(lang => (
<option key={lang.value} value={lang.value}> <option key={lang.value} value={lang.value}>
{lang.flag} {lang.label} {lang.flag} {lang.label}
@@ -248,18 +249,17 @@ export default function LanguagePage() {
</select> </select>
</div> </div>
{/* Voice Language */}
<div className="space-y-3"> <div className="space-y-3">
<label className="flex text-sm font-medium text-accent items-center gap-2"> <label className="flex text-sm font-medium text-accent items-center gap-2">
<Mic size={16} /> <Mic size={16} />
Voice Language {t("language.voice_language")}
</label> </label>
<select <select
value={selectedVoiceLang} value={selectedVoiceLang}
onChange={(e) => setSelectedVoiceLang(e.target.value)} onChange={(e) => setSelectedVoiceLang(e.target.value)}
className="w-full select select-warning" className="w-full select select-warning"
> >
<option value="">Select voice language...</option> <option value="">{t("language.select_voice_placeholder")}.</option>
{languageOptions.map(lang => ( {languageOptions.map(lang => (
<option key={lang.value} value={lang.value}> <option key={lang.value} value={lang.value}>
{lang.flag} {lang.label} {lang.flag} {lang.label}
@@ -274,22 +274,22 @@ export default function LanguagePage() {
<button <button
onClick={handleSetLanguage} onClick={handleSetLanguage}
disabled={!selectedTextLang || !selectedVoiceLang || isSettingLanguage} disabled={!selectedTextLang || !selectedVoiceLang || isSettingLanguage}
className="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 text-white px-8 py-3 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl disabled:cursor-not-allowed cursor-pointer" className="bg-linear-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 text-white px-8 py-3 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl disabled:cursor-not-allowed cursor-pointer"
> >
<Settings size={20} /> <Settings size={20} />
{isSettingLanguage ? 'Applying...' : 'Apply Language Settings'} {isSettingLanguage ? t("language.btn_applying") : t("language.btn_apply")}
</button> </button>
</div> </div>
</div> </div>
{/* Instructions */} {/* Instructions */}
<div className="bg-info/5 rounded-lg p-4 border border-info/30 mt-6"> <div className="bg-info/5 rounded-lg p-4 border border-info/30 mt-6">
<h3 className="font-medium text-error mb-2">📋 Instructions:</h3> <h3 className="font-medium text-error mb-2">{t("language.inst_title")}</h3>
<ol className="text-sm text-error space-y-1"> <ol className="text-sm text-error space-y-1">
<li>1. Click "Select Game Folder" and choose your game's root directory</li> <li>{t("language.inst_step_1")}</li>
<li>2. Wait for the system to validate the game directory</li> <li>{t("language.inst_step_2")}</li>
<li>3. Select your preferred text and voice languages</li> <li>{t("language.inst_step_3")}</li>
<li>4. Click "Apply Language Settings" to save your changes</li> <li>{t("language.inst_step_4")}</li>
</ol> </ol>
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@ import { Link } from '@tanstack/react-router';
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper'; import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
import UpdateModal from '@/components/updateModal'; import UpdateModal from '@/components/updateModal';
import { BackgroundSelector } from '@/components/backgroudModal'; import { BackgroundSelector } from '@/components/backgroudModal';
import { useTranslation } from 'react-i18next';
export default function LauncherPage() { export default function LauncherPage() {
@@ -26,7 +27,7 @@ export default function LauncherPage() {
proxyVersion, proxyVersion,
background background
} = useSettingStore() } = useSettingStore()
const { t } = useTranslation()
const { const {
isOpenDownloadDataModal, isOpenDownloadDataModal,
isOpenUpdateDataModal, isOpenUpdateDataModal,
@@ -156,17 +157,17 @@ export default function LauncherPage() {
const fullPath = `${folderPath}/StarRail_Data/StreamingAssets/DesignData/Windows` const fullPath = `${folderPath}/StarRail_Data/StreamingAssets/DesignData/Windows`
const exists = await FSService.DirExists(fullPath) const exists = await FSService.DirExists(fullPath)
if (!exists) { if (!exists) {
toast.error('Game directory not found. Please select the correct folder.') toast.error(t("home.error_game_dir"))
} else { } else {
setGamePath(basePath) setGamePath(basePath)
setGameDir(folderPath) setGameDir(folderPath)
toast.success('Game path set successfully') toast.success(t("home.game_path_success"))
} }
} else { } else {
toast.error('Not valid file type') toast.error(t("home.error_file_type"))
} }
} catch (err: any) { } catch (err: any) {
toast.error('PickFolder error:', err) toast.error(t("home.toast_pick_folder_error"), err)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
@@ -184,7 +185,7 @@ export default function LauncherPage() {
if (!proxyRunning && !gamePath.endsWith("launcher.exe")) { if (!proxyRunning && !gamePath.endsWith("launcher.exe")) {
const [resultProxy, error] = await FSService.StartWithConsole(proxyPath) const [resultProxy, error] = await FSService.StartWithConsole(proxyPath)
if (!resultProxy) { if (!resultProxy) {
toast.error('Failed to start proxy: ' + error) toast.error(t("home.toast_start_proxy_failed") + error)
return return
} }
setProxyRunning(true) setProxyRunning(true)
@@ -194,7 +195,7 @@ export default function LauncherPage() {
if (!serverRunning) { if (!serverRunning) {
const [resultServer, error] = await FSService.StartWithConsole(serverPath) const [resultServer, error] = await FSService.StartWithConsole(serverPath)
if (!resultServer) { if (!resultServer) {
toast.error('Failed to start server: ' + error) toast.error(t("home.toast_start_server_failed") + error)
return return
} }
setServerRunning(true) setServerRunning(true)
@@ -204,13 +205,13 @@ export default function LauncherPage() {
if (gamePath.endsWith("launcher.exe")) { if (gamePath.endsWith("launcher.exe")) {
const [resultGame, error] = await FSService.StartWithConsole(gamePath) const [resultGame, error] = await FSService.StartWithConsole(gamePath)
if (!resultGame) { if (!resultGame) {
toast.error('Failed to start game: ' + error) toast.error(t("home.toast_start_game_failed") + error)
return return
} }
} else { } else {
const [resultGame, error] = await FSService.StartApp(gamePath) const [resultGame, error] = await FSService.StartApp(gamePath)
if (!resultGame) { if (!resultGame) {
toast.error('Failed to start game: ' + error) toast.error(t("home.toast_start_game_failed") + error)
return return
} }
} }
@@ -218,7 +219,7 @@ export default function LauncherPage() {
} catch (err: any) { } catch (err: any) {
console.log(err) console.log(err)
toast.error('StartGame error:', err) toast.error(t("home.toast_start_game_error"), err)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
@@ -276,7 +277,7 @@ export default function LauncherPage() {
{/* Header */} {/* Header */}
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 "> <header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 ">
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">Firefly GO</div> <div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">{t("home.header_title")}</div>
</header> </header>
<div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg"> <div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg">
@@ -296,7 +297,7 @@ export default function LauncherPage() {
</div> </div>
))} ))}
<div className="tooltip tooltip-left" data-tip="How to use all tools & commands"> <div className="tooltip tooltip-left" data-tip={t("home.tooltip_how_to")}>
<Link <Link
to="/howto" to="/howto"
className="btn btn-warning btn-circle" className="btn btn-warning btn-circle"
@@ -312,7 +313,7 @@ export default function LauncherPage() {
{/* Bottom Panel */} {/* Bottom Panel */}
{serverReady && proxyReady && !isDownloading && ( {serverReady && proxyReady && !isDownloading && (
<div className="fixed bottom-0 right-0 p-8 z-10"> <div className="fixed bottom-0 right-0 p-8 z-2">
<div className="flex flex-wrap items-center justify-center gap-2"> <div className="flex flex-wrap items-center justify-center gap-2">
{gamePath === "" ? ( {gamePath === "" ? (
<button <button
@@ -320,7 +321,7 @@ export default function LauncherPage() {
onClick={handlePickFile} onClick={handlePickFile}
> >
<FolderOpen className="w-6 h-6" /> <FolderOpen className="w-6 h-6" />
{isLoading ? 'Selecting...' : 'Select Game file'} {isLoading ? t("home.btn_selecting") : t("home.btn_select_game")}
</button> </button>
) : ( ) : (
<button <button
@@ -328,7 +329,7 @@ export default function LauncherPage() {
onClick={handleStartGame} onClick={handleStartGame}
> >
<Play className="w-6 h-6" /> <Play className="w-6 h-6" />
{isLoading ? 'Selecting...' : gameRunning ? 'Game is running' : 'Start Game'} {isLoading ? t("home.btn_selecting") : gameRunning ? t("home.btn_game_running") : t("home.btn_start_game")}
</button> </button>
)} )}
@@ -337,7 +338,7 @@ export default function LauncherPage() {
<Menu className="w-6 h-6" /> <Menu className="w-6 h-6" />
</div> </div>
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"> <ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
<li><button onClick={handlePickFile}>Change Game Path</button></li> <li><button onClick={handlePickFile}>{t("home.menu_change_path")}</button></li>
<li> <li>
<button <button
onClick={async () => { onClick={async () => {
@@ -357,29 +358,44 @@ export default function LauncherPage() {
setIsOpenUpdateDataModal(true) setIsOpenUpdateDataModal(true)
return return
} }
toast.success("No updates available") toast.success(t("home.no_updates"))
}}> }}>
Check for Updates Server & Proxy {t("home.menu_check_update")}
</button> </button>
</li> </li>
<li> <li>
</li> </li>
<li><button disabled={!serverPath} onClick={() => { <li>
<button disabled={!serverPath} onClick={() => {
if (serverPath) { if (serverPath) {
FSService.OpenFolder("./server") FSService.OpenFolder("./server")
} }
}}>Open server folder</button></li> }}
<li><button disabled={!proxyPath} onClick={() => { >
{t("home.menu_open_server")}
</button>
</li>
<li>
<button disabled={!proxyPath} onClick={() => {
if (proxyPath) { if (proxyPath) {
FSService.OpenFolder("./proxy") FSService.OpenFolder("./proxy")
} }
}}>Open proxy folder</button></li> }}
<li><button disabled={!gameDir} onClick={() => { >
{t("home.menu_open_proxy")}
</button>
</li>
<li>
<button disabled={!gameDir} onClick={() => {
if (gameDir) { if (gameDir) {
FSService.OpenFolder(gameDir + "/StarRail_Data/Persistent/Audio/AudioPackage/Windows") FSService.OpenFolder(gameDir + "/StarRail_Data/Persistent/Audio/AudioPackage/Windows")
} }
}}>Open voice folder</button></li> }}
>
{t("home.menu_open_voice")}
</button>
</li>
</ul> </ul>
</div> </div>
@@ -412,7 +428,7 @@ export default function LauncherPage() {
/> />
</div> </div>
<div className="text-center text-xs text-white/60"> <div className="text-center text-xs text-white/60">
{progressDownload < 100 ? 'Please wait...' : 'Complete!'} {progressDownload < 100 ? t("home.status_wait") : t("home.status_complete")}
</div> </div>
</div> </div>
</div> </div>
@@ -431,16 +447,16 @@ export default function LauncherPage() {
: "text-red-200 text-xl" : "text-red-200 text-xl"
}`} }`}
> >
{downloadType === "update:launcher:downloading" && "Updating launcher"} {downloadType === "update:launcher:downloading" && t("home.status_updating_launcher")}
{downloadType === "update:launcher:success" && "Launcher updated successfully, auto closing after 5s"} {downloadType === "update:launcher:success" && t("home.status_update_success")}
{downloadType === "update:launcher:failed" && "Launcher update failed, auto closing after 5s"} {downloadType === "update:launcher:failed" && t("home.status_update_failed")}
<span className="dot-animation ml-1"></span> <span className="dot-animation ml-1"></span>
</span> </span>
</div> </div>
)} )}
<div className="text-xs text-white/60"> <div className="text-xs text-white/60">
{progressDownload < 100 ? "Please wait..." : "Complete!"} {progressDownload < 100 ? t("home.status_wait") : t("home.status_complete")}
</div> </div>
</div> </div>
@@ -470,32 +486,32 @@ export default function LauncherPage() {
<UpdateModal <UpdateModal
isOpen={isOpenUpdateDataModal} isOpen={isOpenUpdateDataModal}
onClose={() => setIsOpenUpdateDataModal(false)} onClose={() => setIsOpenUpdateDataModal(false)}
title="Update Data" title={t("home.modal_update_title")}
message="Do you want to update data server and proxy?" message={t("home.modal_update_msg")}
buttons={[ buttons={[
{ text: "No", onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" }, { text: t("home.btn_no"), onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" },
{ text: "Yes", onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" } { text: t("home.btn_yes"), onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" }
]} ]}
/> />
<UpdateModal <UpdateModal
isOpen={isOpenDownloadDataModal} isOpen={isOpenDownloadDataModal}
onClose={() => setIsOpenDownloadDataModal(false)} onClose={() => setIsOpenDownloadDataModal(false)}
title="Download Data" title={t("home.modal_download_title")}
message="Data server and proxy download required" message={t("home.modal_download_msg")}
buttons={[ buttons={[
{ text: "Download", onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" } { text: t("home.btn_download"), onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" }
]} ]}
/> />
<UpdateModal <UpdateModal
isOpen={isOpenSelfUpdateModal} isOpen={isOpenSelfUpdateModal}
onClose={() => setIsOpenSelfUpdateModal(false)} onClose={() => setIsOpenSelfUpdateModal(false)}
title="Update Launcher" title={t("home.modal_self_update_title")}
message="Do you want to update launcher?" message={t("home.modal_self_update_msg")}
buttons={[ buttons={[
{ text: "No", onClick: () => setIsOpenSelfUpdateModal(false), variant: "outline" }, { text: t("home.btn_no"), onClick: () => setIsOpenSelfUpdateModal(false), variant: "outline" },
{ text: "Yes", onClick: async () => { setIsOpenSelfUpdateModal(false); await handlerUpdateData() }, variant: "primary" } { text: t("home.btn_yes"), onClick: async () => { setIsOpenSelfUpdateModal(false); await handlerUpdateData() }, variant: "primary" }
]} ]}
/> />

View File

@@ -1,6 +1,7 @@
export default function getCroppedImg(imageSrc: string, crop: any): Promise<string> { export default function getCroppedImg(imageSrc: string, crop: any): Promise<string> {
const image = new Image() const image = new Image()
image.crossOrigin = "anonymous"
image.src = imageSrc image.src = imageSrc
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')! const ctx = canvas.getContext('2d')!

View File

@@ -10,7 +10,7 @@ const ProxyFile = "firefly-go-proxy.exe"
const LauncherFile = "firefly-launcher.exe" const LauncherFile = "firefly-launcher.exe"
const TempUrl = "./temp" const TempUrl = "./temp"
const CurrentLauncherVersion = "2.4.1" const CurrentLauncherVersion = "2.5.0"
type ToolFile string type ToolFile string