diff options
| author | sana2dang <sana2dang@naver.com> | 2026-03-12 16:07:04 +0900 |
|---|---|---|
| committer | sana2dang <sana2dang@naver.com> | 2026-03-12 16:07:04 +0900 |
| commit | f4e2769d069fcf87da5ec7f39e0d372903bdb810 (patch) | |
| tree | 373af291caf42538b20d14a0ff943ed6c83e670c /src | |
| parent | 97a70cd68813f2174fe145ee79784e509d11a742 (diff) | |
| download | decky-lsfg-vk-f4e2769d069fcf87da5ec7f39e0d372903bdb810.tar.gz decky-lsfg-vk-f4e2769d069fcf87da5ec7f39e0d372903bdb810.zip | |
Apply i18n to all UI components with ko/ja translations
- Add src/i18n/i18n.ts translation engine (ported from SimpleDeckyTDP)
- Add defaults/i18n/{ko,ja,template}.json with 80 translation keys
- Add defaults/i18n/language_metadata.json and steam_language_map.json
- Add scripts/build_i18n_json.sh to generate languages.json
- Apply t() to all 10 components: Content, ConfigurationSection,
FlatpaksModal, FpsMultiplierControl, InstallationButton, NerdStuffModal,
ProfileManagement, SmartClipboardButton, FgmodClipboardButton, UsageInstructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/ConfigurationSection.tsx | 53 | ||||
| -rw-r--r-- | src/components/Content.tsx | 7 | ||||
| -rw-r--r-- | src/components/FgmodClipboardButton.tsx | 3 | ||||
| -rw-r--r-- | src/components/FlatpaksModal.tsx | 91 | ||||
| -rw-r--r-- | src/components/FpsMultiplierControl.tsx | 3 | ||||
| -rw-r--r-- | src/components/InstallationButton.tsx | 9 | ||||
| -rw-r--r-- | src/components/NerdStuffModal.tsx | 31 | ||||
| -rw-r--r-- | src/components/ProfileManagement.tsx | 41 | ||||
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 3 | ||||
| -rw-r--r-- | src/components/UsageInstructions.tsx | 7 | ||||
| -rw-r--r-- | src/i18n/i18n.ts | 65 | ||||
| -rw-r--r-- | src/i18n/languages.json | 351 |
12 files changed, 545 insertions, 119 deletions
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 0734297..c988f9f 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -7,6 +7,7 @@ import { EXPERIMENTAL_PRESENT_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK } from "../config/generatedConfigSchema"; +import t from '../i18n/i18n'; interface ConfigurationSectionProps { config: ConfigurationData; @@ -84,7 +85,7 @@ export function ConfigurationSection({ color: "white" }} > - Config + {t('CONFIG_SECTION_TITLE', 'Config')} </div> </PanelSectionRow> @@ -115,8 +116,8 @@ export function ConfigurationSection({ <> <PanelSectionRow> <SliderField - label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} - description="Lowers internal motion estimation resolution, improving performance slightly" + label={`${t('CONFIG_FLOW_SCALE', 'Flow Scale')} (${Math.round(config.flow_scale * 100)}%)`} + description={t('CONFIG_FLOW_SCALE_DESC', 'Lowers internal motion estimation resolution, improving performance slightly')} value={config.flow_scale} min={0.25} max={1.0} @@ -127,8 +128,8 @@ export function ConfigurationSection({ <PanelSectionRow> <SliderField - label={`Base FPS Cap${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : " (Off)"}`} - description="Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)" + label={`${t('CONFIG_BASE_FPS_CAP', 'Base FPS Cap')}${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : ` (${t('CONFIG_BASE_FPS_CAP_OFF', 'Off')})`}`} + description={t('CONFIG_BASE_FPS_CAP_DESC', 'Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)')} value={config.dxvk_frame_rate} min={0} max={60} @@ -139,8 +140,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label={`Present Mode (${(config.experimental_present_mode || "fifo") === "fifo" ? "FIFO - VSync" : "Mailbox"})`} - description="Toggle between FIFO - VSync (default) and Mailbox presentation modes for better performance or compatibility" + label={`${t('CONFIG_PRESENT_MODE', 'Present Mode')} (${(config.experimental_present_mode || "fifo") === "fifo" ? t('CONFIG_PRESENT_MODE_FIFO', 'FIFO - VSync') : t('CONFIG_PRESENT_MODE_MAILBOX', 'Mailbox')})`} + description={t('CONFIG_PRESENT_MODE_DESC', 'Toggle between FIFO - VSync (default) and Mailbox presentation modes for better performance or compatibility')} checked={(config.experimental_present_mode || "fifo") === "fifo"} onChange={(value) => onConfigChange(EXPERIMENTAL_PRESENT_MODE, value ? "fifo" : "mailbox")} /> @@ -148,8 +149,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Performance Mode" - description="Uses a lighter model for FG (Recommended for most games)" + label={t('CONFIG_PERFORMANCE_MODE', 'Performance Mode')} + description={t('CONFIG_PERFORMANCE_MODE_DESC', 'Uses a lighter model for FG (Recommended for most games)')} checked={config.performance_mode} onChange={(value) => onConfigChange(PERFORMANCE_MODE, value)} /> @@ -157,8 +158,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="HDR Mode" - description="Enables HDR mode (only for games that support HDR)" + label={t('CONFIG_HDR_MODE', 'HDR Mode')} + description={t('CONFIG_HDR_MODE_DESC', 'Enables HDR mode (only for games that support HDR)')} checked={config.hdr_mode} onChange={(value) => onConfigChange(HDR_MODE, value)} /> @@ -179,7 +180,7 @@ export function ConfigurationSection({ color: "white" }} > - Workarounds + {t('CONFIG_WORKAROUNDS_TITLE', 'Workarounds')} </div> </PanelSectionRow> @@ -210,8 +211,8 @@ export function ConfigurationSection({ <> <PanelSectionRow> <ToggleField - label="Enable WSI" - description="Re-Enable Gamescope WSI Layer. Requires game restart to apply." + label={t('CONFIG_ENABLE_WSI', 'Enable WSI')} + description={t('CONFIG_ENABLE_WSI_DESC', 'Re-Enable Gamescope WSI Layer. Requires game restart to apply.')} checked={config.enable_wsi} onChange={(value) => onConfigChange(ENABLE_WSI, value)} /> @@ -219,8 +220,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Enable WOW64 for 32-bit games" - description="Enables PROTON_USE_WOW64=1 for 32-bit games (Use with ProtonGE to fix crashing)" + label={t('CONFIG_ENABLE_WOW64', 'Enable WOW64 for 32-bit games')} + description={t('CONFIG_ENABLE_WOW64_DESC', 'Enables PROTON_USE_WOW64=1 for 32-bit games (Use with ProtonGE to fix crashing)')} checked={config.enable_wow64} onChange={(value) => onConfigChange('enable_wow64', value)} /> @@ -228,8 +229,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Disable Steam Deck Mode" - description="Disables Steam Deck mode (Unlocks hidden settings in some games)" + label={t('CONFIG_DISABLE_STEAMDECK_MODE', 'Disable Steam Deck Mode')} + description={t('CONFIG_DISABLE_STEAMDECK_MODE_DESC', 'Disables Steam Deck mode (Unlocks hidden settings in some games)')} checked={config.disable_steamdeck_mode} onChange={(value) => onConfigChange(DISABLE_STEAMDECK_MODE, value)} /> @@ -237,8 +238,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="MangoHud Workaround" - description="Enables a transparent mangohud overlay, sometimes fixes issues with 2X multiplier in game mode" + label={t('CONFIG_MANGOHUD_WORKAROUND', 'MangoHud Workaround')} + description={t('CONFIG_MANGOHUD_WORKAROUND_DESC', 'Enables a transparent mangohud overlay, sometimes fixes issues with 2X multiplier in game mode')} checked={config.mangohud_workaround} onChange={(value) => onConfigChange(MANGOHUD_WORKAROUND, value)} /> @@ -246,8 +247,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Disable vkBasalt" - description="Disables vkBasalt layer which can conflict with LSFG (Reshade, some Decky plugins)" + label={t('CONFIG_DISABLE_VKBASALT', 'Disable vkBasalt')} + description={t('CONFIG_DISABLE_VKBASALT_DESC', 'Disables vkBasalt layer which can conflict with LSFG (Reshade, some Decky plugins)')} checked={config.disable_vkbasalt} disabled={config.force_enable_vkbasalt} onChange={(value) => { @@ -262,8 +263,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Force Enable vkBasalt" - description="Force vkBasalt to engage to fix framepacing issues in gamemode" + label={t('CONFIG_FORCE_ENABLE_VKBASALT', 'Force Enable vkBasalt')} + description={t('CONFIG_FORCE_ENABLE_VKBASALT_DESC', 'Force vkBasalt to engage to fix framepacing issues in gamemode')} checked={config.force_enable_vkbasalt} disabled={config.disable_vkbasalt} onChange={(value) => { @@ -278,8 +279,8 @@ export function ConfigurationSection({ <PanelSectionRow> <ToggleField - label="Enable Zink for OpenGL Games" - description="Use Vulkan-based OpenGL implementation for OpenGL games (may cause crashes or freezes with some games)" + label={t('CONFIG_ENABLE_ZINK', 'Enable Zink for OpenGL Games')} + description={t('CONFIG_ENABLE_ZINK_DESC', 'Use Vulkan-based OpenGL implementation for OpenGL games (may cause crashes or freezes with some games)')} checked={config.enable_zink} onChange={(value) => onConfigChange(ENABLE_ZINK, value)} /> diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 3dc2696..c785a50 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -14,6 +14,7 @@ import { FpsMultiplierControl } from "./FpsMultiplierControl"; import { NerdStuffModal } from "./NerdStuffModal"; import { FlatpaksModal } from "./FlatpaksModal"; import { ConfigurationData } from "../config/configSchema"; +import t from '../i18n/i18n'; export function Content() { const { @@ -108,7 +109,7 @@ export function Content() { color: "white" }} > - FPS Multiplier + {t('CONTENT_FPS_MULTIPLIER', 'FPS Multiplier')} </div> </PanelSectionRow> @@ -150,7 +151,7 @@ export function Content() { layout="below" onClick={handleShowNerdStuff} > - Nerd Stuff + {t('CONTENT_NERD_STUFF', 'Nerd Stuff')} </ButtonItem> </PanelSectionRow> @@ -159,7 +160,7 @@ export function Content() { layout="below" onClick={handleShowFlatpaks} > - Flatpak Setup + {t('CONTENT_FLATPAK_SETUP', 'Flatpak Setup')} </ButtonItem> </PanelSectionRow> diff --git a/src/components/FgmodClipboardButton.tsx b/src/components/FgmodClipboardButton.tsx index 6f65955..efc5490 100644 --- a/src/components/FgmodClipboardButton.tsx +++ b/src/components/FgmodClipboardButton.tsx @@ -4,6 +4,7 @@ import { FaClipboard, FaCheck } from "react-icons/fa"; import { checkFgmodDirectory } from "../api/lsfgApi"; import { showClipboardErrorToast } from "../utils/toastUtils"; import { copyWithVerification } from "../utils/clipboardUtils"; +import t from '../i18n/i18n'; export function FgmodClipboardButton() { const [isLoading, setIsLoading] = useState(false); @@ -93,7 +94,7 @@ export function FgmodClipboardButton() { color: showSuccess ? "#4CAF50" : "inherit", fontWeight: showSuccess ? "bold" : "normal" }}> - {showSuccess ? "Copied to clipboard" : isLoading ? "Copying..." : "LSFG + DeckyFG"} + {showSuccess ? t('CLIPBOARD_COPIED', 'Copied to clipboard') : isLoading ? t('CLIPBOARD_COPYING', 'Copying...') : t('CLIPBOARD_LSFG_FGMOD', 'LSFG + DeckyFG')} </div> </div> </ButtonItem> diff --git a/src/components/FlatpaksModal.tsx b/src/components/FlatpaksModal.tsx index a80fff0..16d0369 100644 --- a/src/components/FlatpaksModal.tsx +++ b/src/components/FlatpaksModal.tsx @@ -27,6 +27,7 @@ import { FlatpakApp, FlatpakAppInfo } from '../api/lsfgApi'; +import t from '../i18n/i18n'; interface FlatpaksModalProps { closeModal?: () => void; @@ -116,7 +117,7 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { if (loading) { return ( <ModalRoot closeModal={closeModal}> - <DialogHeader>Flatpak Extensions</DialogHeader> + <DialogHeader>{t('FLATPAK_MODAL_TITLE', 'Flatpak Extensions')}</DialogHeader> <DialogBody> <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}> <Spinner /> @@ -129,17 +130,17 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { const instructionSteps = [ { id: 'try-first', - title: 'Try first:', + title: t('FLATPAK_STEP_TRY_FIRST', 'Try first:'), command: '~/lsfg' }, { id: 'try-full-path', - title: "If that doesn't work, try full path:", + title: t('FLATPAK_STEP_TRY_FULL_PATH', "If that doesn't work, try full path:"), command: '/home/(username)/lsfg' }, { id: 'final-result', - title: 'Final result should look like:', + title: t('FLATPAK_STEP_FINAL', 'Final result should look like:'), command: '~/lsfg "usr/bin/flatpak"' } ]; @@ -162,20 +163,20 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { return ( <ModalRoot closeModal={closeModal}> - <DialogHeader>Flatpak Extensions</DialogHeader> + <DialogHeader>{t('FLATPAK_MODAL_TITLE', 'Flatpak Extensions')}</DialogHeader> <DialogBody> <Focusable> {/* Extension Status Section */} <DialogControlsSection> - <DialogControlsSectionHeader>Runtime Extension Installer</DialogControlsSectionHeader> + <DialogControlsSectionHeader>{t('FLATPAK_RUNTIME_INSTALLER', 'Runtime Extension Installer')}</DialogControlsSectionHeader> {extensionStatus && extensionStatus.success ? ( <> {/* 23.08 Runtime */} <PanelSectionRow> - <Field - label="Runtime 23.08" - description={extensionStatus.installed_23_08 ? "Installed" : "Not installed"} + <Field + label={t('FLATPAK_RUNTIME_23', 'Runtime 23.08')} + description={extensionStatus.installed_23_08 ? t('FLATPAK_INSTALLED', 'Installed') : t('FLATPAK_NOT_INSTALLED', 'Not installed')} icon={extensionStatus.installed_23_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />} > <ButtonItem @@ -187,8 +188,8 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { if (operation === 'uninstall') { confirmOperation( action, - 'Uninstall Runtime Extension', - 'Are you sure you want to uninstall the 23.08 runtime extension?' + t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), + `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 23.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); @@ -200,11 +201,11 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { <Spinner /> ) : extensionStatus.installed_23_08 ? ( <> - <FaTrash /> Uninstall + <FaTrash /> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} </> ) : ( <> - <FaDownload /> Install + <FaDownload /> {t('FLATPAK_INSTALL_BTN', 'Install')} </> )} </ButtonItem> @@ -213,9 +214,9 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { {/* 24.08 Runtime */} <PanelSectionRow> - <Field - label="Runtime 24.08" - description={extensionStatus.installed_24_08 ? "Installed" : "Not installed"} + <Field + label={t('FLATPAK_RUNTIME_24', 'Runtime 24.08')} + description={extensionStatus.installed_24_08 ? t('FLATPAK_INSTALLED', 'Installed') : t('FLATPAK_NOT_INSTALLED', 'Not installed')} icon={extensionStatus.installed_24_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />} > <ButtonItem @@ -227,8 +228,8 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { if (operation === 'uninstall') { confirmOperation( action, - 'Uninstall Runtime Extension', - 'Are you sure you want to uninstall the 24.08 runtime extension?' + t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), + `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 24.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); @@ -240,11 +241,11 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { <Spinner /> ) : extensionStatus.installed_24_08 ? ( <> - <FaTrash /> Uninstall + <FaTrash /> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} </> ) : ( <> - <FaDownload /> Install + <FaDownload /> {t('FLATPAK_INSTALL_BTN', 'Install')} </> )} </ButtonItem> @@ -253,9 +254,9 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { {/* 25.08 Runtime */} <PanelSectionRow> - <Field - label="Runtime 25.08" - description={extensionStatus.installed_25_08 ? "Installed" : "Not installed"} + <Field + label={t('FLATPAK_RUNTIME_25', 'Runtime 25.08')} + description={extensionStatus.installed_25_08 ? t('FLATPAK_INSTALLED', 'Installed') : t('FLATPAK_NOT_INSTALLED', 'Not installed')} icon={extensionStatus.installed_25_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />} > <ButtonItem @@ -267,8 +268,8 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { if (operation === 'uninstall') { confirmOperation( action, - 'Uninstall Runtime Extension', - 'Are you sure you want to uninstall the 25.08 runtime extension?' + t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), + `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 25.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); @@ -280,11 +281,11 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { <Spinner /> ) : extensionStatus.installed_25_08 ? ( <> - <FaTrash /> Uninstall + <FaTrash /> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} </> ) : ( <> - <FaDownload /> Install + <FaDownload /> {t('FLATPAK_INSTALL_BTN', 'Install')} </> )} </ButtonItem> @@ -293,9 +294,9 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { </> ) : ( <PanelSectionRow> - <Field - label="Error" - description={extensionStatus?.error || 'Failed to check extension status'} + <Field + label={t('FLATPAK_ERROR', 'Error')} + description={extensionStatus?.error || t('FLATPAK_ERROR_STATUS', 'Failed to check extension status')} icon={<FaTimes style={{color: 'red'}} />} /> </PanelSectionRow> @@ -304,7 +305,7 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { {/* Flatpak Apps Section */} <DialogControlsSection> - <DialogControlsSectionHeader>Flatpak Applications</DialogControlsSectionHeader> + <DialogControlsSectionHeader>{t('FLATPAK_APPS_TITLE', 'Flatpak Applications')}</DialogControlsSectionHeader> {flatpakApps && flatpakApps.success ? ( flatpakApps.apps.length > 0 ? ( @@ -313,14 +314,14 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { const partialOverrides = app.has_filesystem_override || app.has_env_override; let statusColor = 'red'; - let statusText = 'No overrides'; + let statusText = t('FLATPAK_STATUS_NO_OVERRIDES', 'No overrides'); if (hasOverrides) { statusColor = 'green'; - statusText = 'Configured'; + statusText = t('FLATPAK_STATUS_CONFIGURED', 'Configured'); } else if (partialOverrides) { statusColor = 'orange'; - statusText = 'Partial'; + statusText = t('FLATPAK_STATUS_PARTIAL', 'Partial'); } return ( @@ -341,17 +342,17 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { }) ) : ( <PanelSectionRow> - <Field - label="No Flatpak Apps Found" - description="No Flatpak applications are currently installed" + <Field + label={t('FLATPAK_NO_APPS', 'No Flatpak Apps Found')} + description={t('FLATPAK_NO_APPS_DESC', 'No Flatpak applications are currently installed')} /> </PanelSectionRow> ) ) : ( <PanelSectionRow> - <Field - label="Error" - description={flatpakApps?.error || 'Failed to load Flatpak applications'} + <Field + label={t('FLATPAK_ERROR', 'Error')} + description={flatpakApps?.error || t('FLATPAK_ERROR_APPS', 'Failed to load Flatpak applications')} icon={<FaTimes style={{color: 'red'}} />} /> </PanelSectionRow> @@ -360,7 +361,7 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { {/* Steam Configuration Instructions */} <DialogControlsSection> - <DialogControlsSectionHeader>Steam Configuration</DialogControlsSectionHeader> + <DialogControlsSectionHeader>{t('FLATPAK_STEAM_CONFIG_TITLE', 'Steam Configuration')}</DialogControlsSectionHeader> <div style={{ padding: '12px', @@ -372,13 +373,13 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { }} > <div style={{ fontWeight: 'bold', marginBottom: '8px', color: '#fff' }}> - Configure Steam Flatpak Shortcuts + {t('FLATPAK_STEAM_CONFIG_HEADER', 'Configure Steam Flatpak Shortcuts')} </div> <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '8px' }}> - In Steam, open your flatpak game and click the cog wheel. + {t('FLATPAK_STEAM_CONFIG_DESC', 'In Steam, open your flatpak game and click the cog wheel.')} </div> <div style={{ fontSize: '0.9em', lineHeight: '1.4', marginBottom: '12px', color: '#ffa500' }}> - <strong>IMPORTANT:</strong> Set this in TARGET (NOT LAUNCH OPTIONS) + <strong>IMPORTANT:</strong> {t('FLATPAK_STEAM_CONFIG_IMPORTANT', 'Set this in TARGET (NOT LAUNCH OPTIONS)')} </div> {instructionSteps.map((step) => ( @@ -421,7 +422,7 @@ export const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { layout="below" onClick={closeModal} > - Close + {t('FLATPAK_CLOSE', 'Close')} </ButtonItem> </PanelSectionRow> </DialogControlsSection> diff --git a/src/components/FpsMultiplierControl.tsx b/src/components/FpsMultiplierControl.tsx index 5ac31bb..206643e 100644 --- a/src/components/FpsMultiplierControl.tsx +++ b/src/components/FpsMultiplierControl.tsx @@ -1,6 +1,7 @@ import { PanelSectionRow, DialogButton, Focusable } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; import { MULTIPLIER } from "../config/generatedConfigSchema"; +import t from '../i18n/i18n'; interface FpsMultiplierControlProps { config: ConfigurationData; @@ -49,7 +50,7 @@ export function FpsMultiplierControl({ textAlign: "center" }} > - {config.multiplier < 2 ? "OFF" : `${config.multiplier}X`} + {config.multiplier < 2 ? t('MULTIPLIER_OFF', 'OFF') : `${config.multiplier}X`} </div> <DialogButton style={{ diff --git a/src/components/InstallationButton.tsx b/src/components/InstallationButton.tsx index d0f2ce5..7f0ac96 100644 --- a/src/components/InstallationButton.tsx +++ b/src/components/InstallationButton.tsx @@ -1,5 +1,6 @@ import { ButtonItem, PanelSectionRow } from "@decky/ui"; import { FaDownload, FaTrash } from "react-icons/fa"; +import t from '../i18n/i18n'; interface InstallationButtonProps { isInstalled: boolean; @@ -20,7 +21,7 @@ export function InstallationButton({ if (isInstalling) { return ( <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <div>Installing...</div> + <div>{t('INSTALL_INSTALLING', 'Installing...')}</div> </div> ); } @@ -28,7 +29,7 @@ export function InstallationButton({ if (isUninstalling) { return ( <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <div>Uninstalling...</div> + <div>{t('INSTALL_UNINSTALLING', 'Uninstalling...')}</div> </div> ); } @@ -37,7 +38,7 @@ export function InstallationButton({ return ( <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> <FaTrash /> - <div>Uninstall LSFG-VK</div> + <div>{t('INSTALL_UNINSTALL_BTN', 'Uninstall LSFG-VK')}</div> </div> ); } @@ -45,7 +46,7 @@ export function InstallationButton({ return ( <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> <FaDownload /> - <div>Install LSFG-VK</div> + <div>{t('INSTALL_INSTALL_BTN', 'Install LSFG-VK')}</div> </div> ); }; diff --git a/src/components/NerdStuffModal.tsx b/src/components/NerdStuffModal.tsx index b4a79a2..8a79b67 100644 --- a/src/components/NerdStuffModal.tsx +++ b/src/components/NerdStuffModal.tsx @@ -8,6 +8,7 @@ import { ButtonItem } from "@decky/ui"; import { getDllStats, DllStatsResult, getConfigFileContent, getLaunchScriptContent, FileContentResult } from "../api/lsfgApi"; +import t from '../i18n/i18n'; interface NerdStuffModalProps { closeModal?: () => void; @@ -63,7 +64,7 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { return ( <ModalRoot onCancel={closeModal} onOK={closeModal}> {loading && ( - <div>Loading information...</div> + <div>{t('NERD_LOADING', 'Loading information...')}</div> )} {error && ( @@ -79,26 +80,26 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { <div>{dllStats.error || "Failed to get DLL stats"}</div> ) : ( <div> - <Field label="DLL Path"> + <Field label={t('NERD_DLL_PATH', 'DLL Path')}> <Focusable onClick={() => dllStats.dll_path && copyToClipboard(dllStats.dll_path)} onActivate={() => dllStats.dll_path && copyToClipboard(dllStats.dll_path)} > - {dllStats.dll_path || "Not available"} + {dllStats.dll_path || t('NERD_NOT_AVAILABLE', 'Not available')} </Focusable> </Field> - <Field label="DLL SHA256 Hash"> + <Field label={t('NERD_DLL_HASH', 'DLL SHA256 Hash')}> <Focusable onClick={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)} onActivate={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)} > - {dllStats.dll_sha256 ? formatSHA256(dllStats.dll_sha256) : "Not available"} + {dllStats.dll_sha256 ? formatSHA256(dllStats.dll_sha256) : t('NERD_NOT_AVAILABLE', 'Not available')} </Focusable> </Field> {dllStats.dll_source && ( - <Field label="Detection Source"> + <Field label={t('NERD_DETECTION_SOURCE', 'Detection Source')}> <div>{dllStats.dll_source}</div> </Field> )} @@ -109,13 +110,13 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { {/* Launch Script Section */} {scriptContent && ( - <Field label="Launch Script"> + <Field label={t('NERD_LAUNCH_SCRIPT', 'Launch Script')}> {!scriptContent.success ? ( - <div>Script not found: {scriptContent.error}</div> + <div>{t('NERD_SCRIPT_NOT_FOUND_PREFIX', 'Script not found:')} {scriptContent.error}</div> ) : ( <div> <div style={{ marginBottom: "8px", fontSize: "0.9em", opacity: 0.8 }}> - Path: {scriptContent.path} + {t('NERD_PATH_PREFIX', 'Path:')} {scriptContent.path} </div> <Focusable onClick={() => scriptContent.content && copyToClipboard(scriptContent.content)} @@ -130,7 +131,7 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { overflow: "auto", maxHeight: "150px" }}> - {scriptContent.content || "No content"} + {scriptContent.content || t('NERD_NO_CONTENT', 'No content')} </pre> </Focusable> </div> @@ -140,13 +141,13 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { {/* Config File Section */} {configContent && ( - <Field label="Configuration File"> + <Field label={t('NERD_CONFIG_FILE', 'Configuration File')}> {!configContent.success ? ( - <div>Config not found: {configContent.error}</div> + <div>{t('NERD_CONFIG_NOT_FOUND_PREFIX', 'Config not found:')} {configContent.error}</div> ) : ( <div> <div style={{ marginBottom: "8px", fontSize: "0.9em", opacity: 0.8 }}> - Path: {configContent.path} + {t('NERD_PATH_PREFIX', 'Path:')} {configContent.path} </div> <Focusable onClick={() => configContent.content && copyToClipboard(configContent.content)} @@ -160,7 +161,7 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { whiteSpace: "pre-wrap", overflow: "auto" }}> - {configContent.content || "No content"} + {configContent.content || t('NERD_NO_CONTENT', 'No content')} </pre> </Focusable> </div> @@ -175,7 +176,7 @@ export function NerdStuffModal({ closeModal }: NerdStuffModalProps) { layout="below" onClick={closeModal} > - Close + {t('NERD_CLOSE', 'Close')} </ButtonItem> </PanelSectionRow> </DialogControlsSection> diff --git a/src/components/ProfileManagement.tsx b/src/components/ProfileManagement.tsx index 73a40c7..626d54c 100644 --- a/src/components/ProfileManagement.tsx +++ b/src/components/ProfileManagement.tsx @@ -25,6 +25,7 @@ import { ProfileResult } from "../api/lsfgApi"; import { showSuccessToast, showErrorToast } from "../utils/toastUtils"; +import t from '../i18n/i18n'; const PROFILES_COLLAPSED_KEY = 'lsfg-profiles-collapsed'; @@ -63,8 +64,8 @@ function TextInputModal({ <p style={{ marginBottom: "24px" }}>{description}</p> <div style={{ marginBottom: "24px" }}> - <Field - label="Name" + <Field + label={t('PROFILE_NAME_LABEL', 'Name')} childrenLayout="below" childrenContainerWidth="max" > @@ -199,10 +200,10 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa const handleCreateProfile = () => { showModal( <TextInputModal - title="Create New Profile" - description="Enter a name for the new profile. The current profile's settings will be copied." - okText="Create" - cancelText="Cancel" + title={t('PROFILE_CREATE_TITLE', 'Create New Profile')} + description={t('PROFILE_CREATE_DESC', "Enter a name for the new profile. The current profile's settings will be copied.")} + okText={t('PROFILE_CREATE_BTN', 'Create')} + cancelText={t('PROFILE_CANCEL_BTN', 'Cancel')} onOK={(name: string) => { if (name.trim()) { createNewProfile(name.trim()); @@ -237,16 +238,16 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa const handleDeleteProfile = () => { if (selectedProfile === "decky-lsfg-vk") { - showErrorToast("Cannot delete default profile", "The default profile cannot be deleted"); + showErrorToast(t('PROFILE_CANNOT_DELETE_TITLE', 'Cannot delete default profile'), t('PROFILE_CANNOT_DELETE_MSG', 'The default profile cannot be deleted')); return; } showModal( <ConfirmModal - strTitle="Delete Profile" - strDescription={`Are you sure you want to delete the profile "${selectedProfile}"? This action cannot be undone.`} - strOKButtonText="Delete" - strCancelButtonText="Cancel" + strTitle={t('PROFILE_DELETE_TITLE', 'Delete Profile')} + strDescription={`${t('PROFILE_DELETE_DESC_PREFIX', 'Are you sure you want to delete the profile')} "${selectedProfile}"${t('PROFILE_DELETE_DESC_SUFFIX', '? This action cannot be undone.')}`} + strOKButtonText={t('PROFILE_DELETE_BTN', 'Delete')} + strCancelButtonText={t('PROFILE_CANCEL_BTN', 'Cancel')} onOK={() => deleteSelectedProfile()} /> ); @@ -284,17 +285,17 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa const handleRenameProfile = () => { if (selectedProfile === "decky-lsfg-vk") { - showErrorToast("Cannot rename default profile", "The default profile cannot be renamed"); + showErrorToast(t('PROFILE_CANNOT_RENAME_TITLE', 'Cannot rename default profile'), t('PROFILE_CANNOT_RENAME_MSG', 'The default profile cannot be renamed')); return; } showModal( <TextInputModal - title="Rename Profile" - description={`Enter a new name for the profile "${selectedProfile}".`} + title={t('PROFILE_RENAME_TITLE', 'Rename Profile')} + description={`${t('PROFILE_RENAME_DESC_PREFIX', 'Enter a new name for the profile')} "${selectedProfile}".`} defaultValue={selectedProfile} - okText="Rename" - cancelText="Cancel" + okText={t('PROFILE_RENAME_BTN', 'Rename')} + cancelText={t('PROFILE_CANCEL_BTN', 'Cancel')} onOK={(newName: string) => { if (newName.trim() && newName.trim() !== selectedProfile) { renameSelectedProfile(newName.trim()); @@ -330,11 +331,11 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa const profileOptions: DropdownOption[] = [ ...profiles.map((profile: string) => ({ data: profile, - label: profile === "decky-lsfg-vk" ? "Default" : profile + label: profile === "decky-lsfg-vk" ? t('PROFILE_DEFAULT', 'Default') : profile })), { data: "__NEW_PROFILE__", - label: "New Profile" + label: t('PROFILE_NEW', 'New Profile') } ]; @@ -361,7 +362,7 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa border: "1px solid rgba(0, 255, 0, 0.3)", fontSize: "13px" }}> - <strong>{mainRunningApp.display_name}</strong> running. Close game to change profile. + <strong>{mainRunningApp.display_name}</strong> running. {t('PROFILE_CLOSE_GAME', 'Close game to change profile.')} </div> </PanelSectionRow> )} @@ -378,7 +379,7 @@ export function ProfileManagement({ currentProfile, onProfileChange }: ProfileMa color: "white" }} > - Profile: {selectedProfile === "decky-lsfg-vk" ? "Default" : selectedProfile} + {t('PROFILE_SECTION_TITLE', 'Profile:')} {selectedProfile === "decky-lsfg-vk" ? t('PROFILE_DEFAULT', 'Default') : selectedProfile} </div> </PanelSectionRow> diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index c90515a..8da239a 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -4,6 +4,7 @@ import { FaClipboard, FaCheck } from "react-icons/fa"; import { getLaunchOption } from "../api/lsfgApi"; import { showClipboardErrorToast } from "../utils/toastUtils"; import { copyWithVerification } from "../utils/clipboardUtils"; +import t from '../i18n/i18n'; export function SmartClipboardButton() { const [isLoading, setIsLoading] = useState(false); @@ -74,7 +75,7 @@ export function SmartClipboardButton() { color: showSuccess ? "#4CAF50" : "inherit", fontWeight: showSuccess ? "bold" : "normal" }}> - {showSuccess ? "Copied to clipboard" : isLoading ? "Copying..." : "Copy Launch Option"} + {showSuccess ? t('CLIPBOARD_COPIED', 'Copied to clipboard') : isLoading ? t('CLIPBOARD_COPYING', 'Copying...') : t('CLIPBOARD_COPY_LAUNCH', 'Copy Launch Option')} </div> </div> </ButtonItem> diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index 0c27517..6d32506 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -1,5 +1,6 @@ import { PanelSectionRow } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; +import t from '../i18n/i18n'; interface UsageInstructionsProps { config: ConfigurationData; @@ -20,7 +21,7 @@ export function UsageInstructions({ config: _config }: UsageInstructionsProps) { color: "white" }} > - Usage Instructions + {t('USAGE_TITLE', 'Usage Instructions')} </div> </PanelSectionRow> @@ -33,7 +34,7 @@ export function UsageInstructions({ config: _config }: UsageInstructionsProps) { whiteSpace: "pre-wrap" }} > - Click "Copy Launch Option" button, then paste it into your Steam game's launch options to enable frame generation. + {t('USAGE_DESC', 'Click "Copy Launch Option" button, then paste it into your Steam game\'s launch options to enable frame generation.')} </div> </PanelSectionRow> @@ -65,7 +66,7 @@ export function UsageInstructions({ config: _config }: UsageInstructionsProps) { marginTop: "8px" }} > -The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running. + {t('USAGE_CONFIG_NOTE', 'The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running.')} </div> </PanelSectionRow> </> diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts new file mode 100644 index 0000000..83ed3da --- /dev/null +++ b/src/i18n/i18n.ts @@ -0,0 +1,65 @@ +// languages.json build via CI build +// to generate for localhost/dev, run `build_i18n_json.sh` script +import * as languages from "./languages.json"; + +function getLangs() { + const langs = languages.language_metadata; + + Object.keys(languages).map((lang) => { + if (lang === "language_metadata" || lang == "steam_language_map") { + return; + } + const strs = languages[lang]; + if (lang && strs && langs[lang]?.name) { + langs[lang].strings = strs; + } + }); + + return langs; +} + +export const LANGS: { + [key: string]: { + name: string; + strings: { + [key: string]: string; + }; + }; +} = getLangs(); + +let cachedLang: string | undefined; + +export const getCurrentLanguage = (): string => { + if (cachedLang) return cachedLang; + + const lang = window.LocalizationManager.m_rgLocalesToUse[0]; + cachedLang = lang; + return lang; +}; + +export const getLanguageName = (lang?: string): string => { + const targetLang = lang || getCurrentLanguage(); + return LANGS[targetLang]?.name || targetLang; +}; + +/** + * Translate a key to the current language + * + * @param key - Translation key + * @param originalString - Original text (fallback) + * @returns Translated string or original text if translation not found + * + * @example + * t('CONTENT_FPS_MULTIPLIER', 'FPS Multiplier') + */ +const t = (key: string, originalString: string): string => { + const lang = getCurrentLanguage(); + + // English always returns the original text + if (lang === "en") return originalString; + + // Return translation if exists, otherwise return original text + return LANGS[lang]?.strings?.[key] ?? originalString; +}; + +export default t; diff --git a/src/i18n/languages.json b/src/i18n/languages.json new file mode 100644 index 0000000..004d71c --- /dev/null +++ b/src/i18n/languages.json @@ -0,0 +1,351 @@ +{ + "ja": { + "CONTENT_FPS_MULTIPLIER": "FPS倍率", + "CONTENT_NERD_STUFF": "詳細情報", + "CONTENT_FLATPAK_SETUP": "Flatpak設定", + "MULTIPLIER_OFF": "オフ", + "CONFIG_SECTION_TITLE": "設定", + "CONFIG_WORKAROUNDS_TITLE": "互換性設定", + "CONFIG_FLOW_SCALE": "フロースケール", + "CONFIG_FLOW_SCALE_DESC": "内部モーション推定解像度を下げて、パフォーマンスをわずかに向上させます", + "CONFIG_BASE_FPS_CAP": "基本FPS上限", + "CONFIG_BASE_FPS_CAP_OFF": "オフ", + "CONFIG_BASE_FPS_CAP_DESC": "フレーム倍率適用前のDirectXゲームの基本フレームレート上限。(ゲームの再起動が必要)", + "CONFIG_PRESENT_MODE": "プレゼンテーションモード", + "CONFIG_PRESENT_MODE_FIFO": "FIFO - VSync", + "CONFIG_PRESENT_MODE_MAILBOX": "Mailbox", + "CONFIG_PRESENT_MODE_DESC": "FIFO - VSync(デフォルト)とMailboxプレゼンテーションモードを切り替えて、パフォーマンスまたは互換性を向上させます", + "CONFIG_PERFORMANCE_MODE": "パフォーマンスモード", + "CONFIG_PERFORMANCE_MODE_DESC": "FGに軽量なモデルを使用します(ほとんどのゲームに推奨)", + "CONFIG_HDR_MODE": "HDRモード", + "CONFIG_HDR_MODE_DESC": "HDRモードを有効化します(HDRをサポートするゲームのみ)", + "CONFIG_ENABLE_WSI": "WSIを有効化", + "CONFIG_ENABLE_WSI_DESC": "Gamescope WSIレイヤーを再有効化します。ゲームの再起動が必要。", + "CONFIG_ENABLE_WOW64": "32ビットゲーム用WOW64を有効化", + "CONFIG_ENABLE_WOW64_DESC": "32ビットゲームにPROTON_USE_WOW64=1を有効化します(ProtonGEと併用してクラッシュを修正)", + "CONFIG_DISABLE_STEAMDECK_MODE": "Steam Deckモードを無効化", + "CONFIG_DISABLE_STEAMDECK_MODE_DESC": "Steam Deckモードを無効化します(一部ゲームの隠し設定を解放)", + "CONFIG_MANGOHUD_WORKAROUND": "MangoHudワークアラウンド", + "CONFIG_MANGOHUD_WORKAROUND_DESC": "透明なMangoHudオーバーレイを有効化します。ゲームモードでの2X倍率問題を修正することがあります", + "CONFIG_DISABLE_VKBASALT": "vkBasaltを無効化", + "CONFIG_DISABLE_VKBASALT_DESC": "LSFGと競合する可能性のあるvkBasaltレイヤーを無効化します(Reshade、一部のDeckyプラグイン)", + "CONFIG_FORCE_ENABLE_VKBASALT": "vkBasaltを強制有効化", + "CONFIG_FORCE_ENABLE_VKBASALT_DESC": "ゲームモードのフレームペーシング問題を修正するためにvkBasaltを強制有効化します", + "CONFIG_ENABLE_ZINK": "OpenGLゲーム用Zinkを有効化", + "CONFIG_ENABLE_ZINK_DESC": "OpenGLゲームにVulkanベースのOpenGL実装を使用します(一部のゲームでクラッシュやフリーズが発生する場合があります)", + "INSTALL_INSTALLING": "インストール中...", + "INSTALL_UNINSTALLING": "アンインストール中...", + "INSTALL_UNINSTALL_BTN": "LSFG-VKをアンインストール", + "INSTALL_INSTALL_BTN": "LSFG-VKをインストール", + "FLATPAK_MODAL_TITLE": "Flatpak拡張", + "FLATPAK_RUNTIME_INSTALLER": "ランタイム拡張インストーラー", + "FLATPAK_RUNTIME_23": "ランタイム 23.08", + "FLATPAK_RUNTIME_24": "ランタイム 24.08", + "FLATPAK_RUNTIME_25": "ランタイム 25.08", + "FLATPAK_INSTALLED": "インストール済み", + "FLATPAK_NOT_INSTALLED": "未インストール", + "FLATPAK_UNINSTALL_TITLE": "ランタイム拡張をアンインストール", + "FLATPAK_UNINSTALL_CONFIRM_PREFIX": "本当に", + "FLATPAK_UNINSTALL_CONFIRM_SUFFIX": "ランタイム拡張をアンインストールしますか?", + "FLATPAK_UNINSTALL_BTN": "アンインストール", + "FLATPAK_INSTALL_BTN": "インストール", + "FLATPAK_APPS_TITLE": "Flatpakアプリケーション", + "FLATPAK_NO_APPS": "Flatpakアプリなし", + "FLATPAK_NO_APPS_DESC": "現在インストールされているFlatpakアプリケーションはありません", + "FLATPAK_STATUS_CONFIGURED": "設定済み", + "FLATPAK_STATUS_PARTIAL": "部分設定", + "FLATPAK_STATUS_NO_OVERRIDES": "オーバーライドなし", + "FLATPAK_ERROR": "エラー", + "FLATPAK_ERROR_STATUS": "拡張ステータスの確認に失敗しました", + "FLATPAK_ERROR_APPS": "Flatpakアプリケーションの読み込みに失敗しました", + "FLATPAK_STEAM_CONFIG_TITLE": "Steam設定", + "FLATPAK_STEAM_CONFIG_HEADER": "Steam Flatpakショートカットの設定", + "FLATPAK_STEAM_CONFIG_DESC": "Steamでflatpakゲームを開き、歯車アイコンをクリックしてください。", + "FLATPAK_STEAM_CONFIG_IMPORTANT": "重要: 起動オプションではなくターゲット(TARGET)に設定してください", + "FLATPAK_STEP_TRY_FIRST": "まず試す:", + "FLATPAK_STEP_TRY_FULL_PATH": "うまくいかない場合、フルパスを試す:", + "FLATPAK_STEP_FINAL": "最終的な結果はこのようになります:", + "FLATPAK_CLOSE": "閉じる", + "NERD_LOADING": "情報を読み込み中...", + "NERD_DLL_PATH": "DLLパス", + "NERD_NOT_AVAILABLE": "利用不可", + "NERD_DLL_HASH": "DLL SHA256ハッシュ", + "NERD_DETECTION_SOURCE": "検出ソース", + "NERD_LAUNCH_SCRIPT": "起動スクリプト", + "NERD_SCRIPT_NOT_FOUND_PREFIX": "スクリプトが見つかりません:", + "NERD_PATH_PREFIX": "パス:", + "NERD_NO_CONTENT": "コンテンツなし", + "NERD_CONFIG_FILE": "設定ファイル", + "NERD_CONFIG_NOT_FOUND_PREFIX": "設定が見つかりません:", + "NERD_CLOSE": "閉じる", + "PROFILE_CLOSE_GAME": "プロファイルを変更するにはゲームを終了してください。", + "PROFILE_SECTION_TITLE": "プロファイル:", + "PROFILE_DEFAULT": "デフォルト", + "PROFILE_NEW": "新しいプロファイル", + "PROFILE_NAME_LABEL": "名前", + "PROFILE_CREATE_TITLE": "新しいプロファイルを作成", + "PROFILE_CREATE_DESC": "新しいプロファイルの名前を入力してください。現在のプロファイルの設定がコピーされます。", + "PROFILE_CREATE_BTN": "作成", + "PROFILE_CANCEL_BTN": "キャンセル", + "PROFILE_RENAME_TITLE": "プロファイルの名前を変更", + "PROFILE_RENAME_DESC_PREFIX": "プロファイルの新しい名前を入力してください:", + "PROFILE_RENAME_BTN": "名前変更", + "PROFILE_CANNOT_DELETE_TITLE": "デフォルトプロファイルは削除できません", + "PROFILE_CANNOT_DELETE_MSG": "デフォルトプロファイルは削除できません", + "PROFILE_DELETE_TITLE": "プロファイルを削除", + "PROFILE_DELETE_DESC_PREFIX": "本当にこのプロファイルを削除しますか?", + "PROFILE_DELETE_DESC_SUFFIX": "この操作は取り消せません。", + "PROFILE_DELETE_BTN": "削除", + "PROFILE_CANNOT_RENAME_TITLE": "デフォルトプロファイルの名前は変更できません", + "PROFILE_CANNOT_RENAME_MSG": "デフォルトプロファイルの名前は変更できません", + "USAGE_TITLE": "使用方法", + "USAGE_DESC": "「起動オプションをコピー」ボタンをクリックし、Steamゲームの起動オプションに貼り付けてフレーム生成を有効化してください。", + "USAGE_CONFIG_NOTE": "設定は~/.config/lsfg-vk/conf.tomlに保存され、ゲーム実行中もホットリロードされます。", + "CLIPBOARD_COPIED": "クリップボードにコピーしました", + "CLIPBOARD_COPYING": "コピー中...", + "CLIPBOARD_COPY_LAUNCH": "起動オプションをコピー", + "CLIPBOARD_LSFG_FGMOD": "LSFG + DeckyFG" + }, + "ko": { + "CONTENT_FPS_MULTIPLIER": "FPS 배율", + "CONTENT_NERD_STUFF": "상세 정보", + "CONTENT_FLATPAK_SETUP": "Flatpak 설정", + "MULTIPLIER_OFF": "끄기", + "CONFIG_SECTION_TITLE": "설정", + "CONFIG_WORKAROUNDS_TITLE": "호환성 설정", + "CONFIG_FLOW_SCALE": "흐름 배율", + "CONFIG_FLOW_SCALE_DESC": "내부 모션 추정 해상도를 낮춰 성능을 약간 향상시킵니다", + "CONFIG_BASE_FPS_CAP": "기본 FPS 상한", + "CONFIG_BASE_FPS_CAP_OFF": "끄기", + "CONFIG_BASE_FPS_CAP_DESC": "프레임 배율 적용 전 DirectX 게임의 기본 프레임 상한. (게임 재시작 필요)", + "CONFIG_PRESENT_MODE": "프레젠테이션 모드", + "CONFIG_PRESENT_MODE_FIFO": "FIFO - VSync", + "CONFIG_PRESENT_MODE_MAILBOX": "Mailbox", + "CONFIG_PRESENT_MODE_DESC": "FIFO - VSync(기본)와 Mailbox 프레젠테이션 모드를 전환하여 성능 또는 호환성을 개선합니다", + "CONFIG_PERFORMANCE_MODE": "성능 모드", + "CONFIG_PERFORMANCE_MODE_DESC": "FG에 더 가벼운 모델을 사용합니다 (대부분의 게임에 권장)", + "CONFIG_HDR_MODE": "HDR 모드", + "CONFIG_HDR_MODE_DESC": "HDR 모드를 활성화합니다 (HDR을 지원하는 게임에만 해당)", + "CONFIG_ENABLE_WSI": "WSI 활성화", + "CONFIG_ENABLE_WSI_DESC": "Gamescope WSI 레이어를 다시 활성화합니다. 게임 재시작 필요.", + "CONFIG_ENABLE_WOW64": "32비트 게임용 WOW64 활성화", + "CONFIG_ENABLE_WOW64_DESC": "32비트 게임에 PROTON_USE_WOW64=1을 활성화합니다 (크래시 수정을 위해 ProtonGE와 함께 사용)", + "CONFIG_DISABLE_STEAMDECK_MODE": "Steam Deck 모드 비활성화", + "CONFIG_DISABLE_STEAMDECK_MODE_DESC": "Steam Deck 모드를 비활성화합니다 (일부 게임의 숨겨진 설정 잠금 해제)", + "CONFIG_MANGOHUD_WORKAROUND": "MangoHud 우회", + "CONFIG_MANGOHUD_WORKAROUND_DESC": "투명한 MangoHud 오버레이를 활성화합니다. 게임 모드에서 2X 배율 문제를 수정하는 데 도움이 될 수 있습니다", + "CONFIG_DISABLE_VKBASALT": "vkBasalt 비활성화", + "CONFIG_DISABLE_VKBASALT_DESC": "LSFG와 충돌할 수 있는 vkBasalt 레이어를 비활성화합니다 (Reshade, 일부 Decky 플러그인)", + "CONFIG_FORCE_ENABLE_VKBASALT": "vkBasalt 강제 활성화", + "CONFIG_FORCE_ENABLE_VKBASALT_DESC": "게임 모드에서 프레임 페이싱 문제 수정을 위해 vkBasalt를 강제 활성화합니다", + "CONFIG_ENABLE_ZINK": "OpenGL 게임에 Zink 활성화", + "CONFIG_ENABLE_ZINK_DESC": "OpenGL 게임에 Vulkan 기반 OpenGL 구현을 사용합니다 (일부 게임에서 크래시나 멈춤이 발생할 수 있습니다)", + "INSTALL_INSTALLING": "설치 중...", + "INSTALL_UNINSTALLING": "제거 중...", + "INSTALL_UNINSTALL_BTN": "LSFG-VK 제거", + "INSTALL_INSTALL_BTN": "LSFG-VK 설치", + "FLATPAK_MODAL_TITLE": "Flatpak 확장", + "FLATPAK_RUNTIME_INSTALLER": "런타임 확장 설치", + "FLATPAK_RUNTIME_23": "런타임 23.08", + "FLATPAK_RUNTIME_24": "런타임 24.08", + "FLATPAK_RUNTIME_25": "런타임 25.08", + "FLATPAK_INSTALLED": "설치됨", + "FLATPAK_NOT_INSTALLED": "설치 안 됨", + "FLATPAK_UNINSTALL_TITLE": "런타임 확장 제거", + "FLATPAK_UNINSTALL_CONFIRM_PREFIX": "정말로", + "FLATPAK_UNINSTALL_CONFIRM_SUFFIX": "런타임 확장을 제거하시겠습니까?", + "FLATPAK_UNINSTALL_BTN": "제거", + "FLATPAK_INSTALL_BTN": "설치", + "FLATPAK_APPS_TITLE": "Flatpak 애플리케이션", + "FLATPAK_NO_APPS": "Flatpak 앱 없음", + "FLATPAK_NO_APPS_DESC": "현재 설치된 Flatpak 애플리케이션이 없습니다", + "FLATPAK_STATUS_CONFIGURED": "설정됨", + "FLATPAK_STATUS_PARTIAL": "부분 설정", + "FLATPAK_STATUS_NO_OVERRIDES": "오버라이드 없음", + "FLATPAK_ERROR": "오류", + "FLATPAK_ERROR_STATUS": "확장 상태 확인 실패", + "FLATPAK_ERROR_APPS": "Flatpak 애플리케이션 로드 실패", + "FLATPAK_STEAM_CONFIG_TITLE": "Steam 설정", + "FLATPAK_STEAM_CONFIG_HEADER": "Steam Flatpak 단축키 설정", + "FLATPAK_STEAM_CONFIG_DESC": "Steam에서 Flatpak 게임을 열고 톱니바퀴를 클릭하세요.", + "FLATPAK_STEAM_CONFIG_IMPORTANT": "중요: 실행 옵션이 아닌 대상(TARGET)에 설정하세요", + "FLATPAK_STEP_TRY_FIRST": "먼저 시도:", + "FLATPAK_STEP_TRY_FULL_PATH": "작동하지 않으면 전체 경로 시도:", + "FLATPAK_STEP_FINAL": "최종 결과는 다음과 같아야 합니다:", + "FLATPAK_CLOSE": "닫기", + "NERD_LOADING": "정보 불러오는 중...", + "NERD_DLL_PATH": "DLL 경로", + "NERD_NOT_AVAILABLE": "사용 불가", + "NERD_DLL_HASH": "DLL SHA256 해시", + "NERD_DETECTION_SOURCE": "감지 소스", + "NERD_LAUNCH_SCRIPT": "실행 스크립트", + "NERD_SCRIPT_NOT_FOUND_PREFIX": "스크립트 없음:", + "NERD_PATH_PREFIX": "경로:", + "NERD_NO_CONTENT": "내용 없음", + "NERD_CONFIG_FILE": "설정 파일", + "NERD_CONFIG_NOT_FOUND_PREFIX": "설정 없음:", + "NERD_CLOSE": "닫기", + "PROFILE_CLOSE_GAME": "프로필 변경을 위해 게임을 종료하세요.", + "PROFILE_SECTION_TITLE": "프로필:", + "PROFILE_DEFAULT": "기본", + "PROFILE_NEW": "새 프로필", + "PROFILE_NAME_LABEL": "이름", + "PROFILE_CREATE_TITLE": "새 프로필 만들기", + "PROFILE_CREATE_DESC": "새 프로필 이름을 입력하세요. 현재 프로필의 설정이 복사됩니다.", + "PROFILE_CREATE_BTN": "만들기", + "PROFILE_CANCEL_BTN": "취소", + "PROFILE_RENAME_TITLE": "프로필 이름 변경", + "PROFILE_RENAME_DESC_PREFIX": "프로필의 새 이름을 입력하세요:", + "PROFILE_RENAME_BTN": "이름 변경", + "PROFILE_CANNOT_DELETE_TITLE": "기본 프로필 삭제 불가", + "PROFILE_CANNOT_DELETE_MSG": "기본 프로필은 삭제할 수 없습니다", + "PROFILE_DELETE_TITLE": "프로필 삭제", + "PROFILE_DELETE_DESC_PREFIX": "정말로 프로필을 삭제하시겠습니까?", + "PROFILE_DELETE_DESC_SUFFIX": "이 작업은 취소할 수 없습니다.", + "PROFILE_DELETE_BTN": "삭제", + "PROFILE_CANNOT_RENAME_TITLE": "기본 프로필 이름 변경 불가", + "PROFILE_CANNOT_RENAME_MSG": "기본 프로필의 이름은 변경할 수 없습니다", + "USAGE_TITLE": "사용 방법", + "USAGE_DESC": "\"실행 옵션 복사\" 버튼을 클릭한 후, Steam 게임의 실행 옵션에 붙여넣어 프레임 생성을 활성화하세요.", + "USAGE_CONFIG_NOTE": "설정은 ~/.config/lsfg-vk/conf.toml에 저장되며 게임 실행 중에도 즉시 반영됩니다.", + "CLIPBOARD_COPIED": "클립보드에 복사됨", + "CLIPBOARD_COPYING": "복사 중...", + "CLIPBOARD_COPY_LAUNCH": "실행 옵션 복사", + "CLIPBOARD_LSFG_FGMOD": "LSFG + DeckyFG" + }, + "language_metadata": { + "ko": { + "name": "한국어" + }, + "en": { + "name": "English" + }, + "ja": { + "name": "日本語" + }, + "zh": { + "name": "中文" + } + }, + "steam_language_map": { + "korean": "ko", + "koreana": "ko", + "english": "en", + "japanese": "ja", + "schinese": "zh", + "tchinese": "zh", + "spanish": "es", + "french": "fr", + "german": "de", + "italian": "it", + "portuguese": "pt", + "russian": "ru" + }, + "template": { + "CONTENT_FPS_MULTIPLIER": "FPS Multiplier", + "CONTENT_NERD_STUFF": "Nerd Stuff", + "CONTENT_FLATPAK_SETUP": "Flatpak Setup", + "MULTIPLIER_OFF": "OFF", + "CONFIG_SECTION_TITLE": "Config", + "CONFIG_WORKAROUNDS_TITLE": "Workarounds", + "CONFIG_FLOW_SCALE": "Flow Scale", + "CONFIG_FLOW_SCALE_DESC": "Lowers internal motion estimation resolution, improving performance slightly", + "CONFIG_BASE_FPS_CAP": "Base FPS Cap", + "CONFIG_BASE_FPS_CAP_OFF": "Off", + "CONFIG_BASE_FPS_CAP_DESC": "Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)", + "CONFIG_PRESENT_MODE": "Present Mode", + "CONFIG_PRESENT_MODE_FIFO": "FIFO - VSync", + "CONFIG_PRESENT_MODE_MAILBOX": "Mailbox", + "CONFIG_PRESENT_MODE_DESC": "Toggle between FIFO - VSync (default) and Mailbox presentation modes for better performance or compatibility", + "CONFIG_PERFORMANCE_MODE": "Performance Mode", + "CONFIG_PERFORMANCE_MODE_DESC": "Uses a lighter model for FG (Recommended for most games)", + "CONFIG_HDR_MODE": "HDR Mode", + "CONFIG_HDR_MODE_DESC": "Enables HDR mode (only for games that support HDR)", + "CONFIG_ENABLE_WSI": "Enable WSI", + "CONFIG_ENABLE_WSI_DESC": "Re-Enable Gamescope WSI Layer. Requires game restart to apply.", + "CONFIG_ENABLE_WOW64": "Enable WOW64 for 32-bit games", + "CONFIG_ENABLE_WOW64_DESC": "Enables PROTON_USE_WOW64=1 for 32-bit games (Use with ProtonGE to fix crashing)", + "CONFIG_DISABLE_STEAMDECK_MODE": "Disable Steam Deck Mode", + "CONFIG_DISABLE_STEAMDECK_MODE_DESC": "Disables Steam Deck mode (Unlocks hidden settings in some games)", + "CONFIG_MANGOHUD_WORKAROUND": "MangoHud Workaround", + "CONFIG_MANGOHUD_WORKAROUND_DESC": "Enables a transparent mangohud overlay, sometimes fixes issues with 2X multiplier in game mode", + "CONFIG_DISABLE_VKBASALT": "Disable vkBasalt", + "CONFIG_DISABLE_VKBASALT_DESC": "Disables vkBasalt layer which can conflict with LSFG (Reshade, some Decky plugins)", + "CONFIG_FORCE_ENABLE_VKBASALT": "Force Enable vkBasalt", + "CONFIG_FORCE_ENABLE_VKBASALT_DESC": "Force vkBasalt to engage to fix framepacing issues in gamemode", + "CONFIG_ENABLE_ZINK": "Enable Zink for OpenGL Games", + "CONFIG_ENABLE_ZINK_DESC": "Use Vulkan-based OpenGL implementation for OpenGL games (may cause crashes or freezes with some games)", + "INSTALL_INSTALLING": "Installing...", + "INSTALL_UNINSTALLING": "Uninstalling...", + "INSTALL_UNINSTALL_BTN": "Uninstall LSFG-VK", + "INSTALL_INSTALL_BTN": "Install LSFG-VK", + "FLATPAK_MODAL_TITLE": "Flatpak Extensions", + "FLATPAK_RUNTIME_INSTALLER": "Runtime Extension Installer", + "FLATPAK_RUNTIME_23": "Runtime 23.08", + "FLATPAK_RUNTIME_24": "Runtime 24.08", + "FLATPAK_RUNTIME_25": "Runtime 25.08", + "FLATPAK_INSTALLED": "Installed", + "FLATPAK_NOT_INSTALLED": "Not installed", + "FLATPAK_UNINSTALL_TITLE": "Uninstall Runtime Extension", + "FLATPAK_UNINSTALL_CONFIRM_PREFIX": "Are you sure you want to uninstall the", + "FLATPAK_UNINSTALL_CONFIRM_SUFFIX": "runtime extension?", + "FLATPAK_UNINSTALL_BTN": "Uninstall", + "FLATPAK_INSTALL_BTN": "Install", + "FLATPAK_APPS_TITLE": "Flatpak Applications", + "FLATPAK_NO_APPS": "No Flatpak Apps Found", + "FLATPAK_NO_APPS_DESC": "No Flatpak applications are currently installed", + "FLATPAK_STATUS_CONFIGURED": "Configured", + "FLATPAK_STATUS_PARTIAL": "Partial", + "FLATPAK_STATUS_NO_OVERRIDES": "No overrides", + "FLATPAK_ERROR": "Error", + "FLATPAK_ERROR_STATUS": "Failed to check extension status", + "FLATPAK_ERROR_APPS": "Failed to load Flatpak applications", + "FLATPAK_STEAM_CONFIG_TITLE": "Steam Configuration", + "FLATPAK_STEAM_CONFIG_HEADER": "Configure Steam Flatpak Shortcuts", + "FLATPAK_STEAM_CONFIG_DESC": "In Steam, open your flatpak game and click the cog wheel.", + "FLATPAK_STEAM_CONFIG_IMPORTANT": "IMPORTANT: Set this in TARGET (NOT LAUNCH OPTIONS)", + "FLATPAK_STEP_TRY_FIRST": "Try first:", + "FLATPAK_STEP_TRY_FULL_PATH": "If that doesn't work, try full path:", + "FLATPAK_STEP_FINAL": "Final result should look like:", + "FLATPAK_CLOSE": "Close", + "NERD_LOADING": "Loading information...", + "NERD_DLL_PATH": "DLL Path", + "NERD_NOT_AVAILABLE": "Not available", + "NERD_DLL_HASH": "DLL SHA256 Hash", + "NERD_DETECTION_SOURCE": "Detection Source", + "NERD_LAUNCH_SCRIPT": "Launch Script", + "NERD_SCRIPT_NOT_FOUND_PREFIX": "Script not found:", + "NERD_PATH_PREFIX": "Path:", + "NERD_NO_CONTENT": "No content", + "NERD_CONFIG_FILE": "Configuration File", + "NERD_CONFIG_NOT_FOUND_PREFIX": "Config not found:", + "NERD_CLOSE": "Close", + "PROFILE_CLOSE_GAME": "Close game to change profile.", + "PROFILE_SECTION_TITLE": "Profile:", + "PROFILE_DEFAULT": "Default", + "PROFILE_NEW": "New Profile", + "PROFILE_NAME_LABEL": "Name", + "PROFILE_CREATE_TITLE": "Create New Profile", + "PROFILE_CREATE_DESC": "Enter a name for the new profile. The current profile's settings will be copied.", + "PROFILE_CREATE_BTN": "Create", + "PROFILE_CANCEL_BTN": "Cancel", + "PROFILE_RENAME_TITLE": "Rename Profile", + "PROFILE_RENAME_DESC_PREFIX": "Enter a new name for the profile", + "PROFILE_RENAME_BTN": "Rename", + "PROFILE_CANNOT_DELETE_TITLE": "Cannot delete default profile", + "PROFILE_CANNOT_DELETE_MSG": "The default profile cannot be deleted", + "PROFILE_DELETE_TITLE": "Delete Profile", + "PROFILE_DELETE_DESC_PREFIX": "Are you sure you want to delete the profile", + "PROFILE_DELETE_DESC_SUFFIX": "? This action cannot be undone.", + "PROFILE_DELETE_BTN": "Delete", + "PROFILE_CANNOT_RENAME_TITLE": "Cannot rename default profile", + "PROFILE_CANNOT_RENAME_MSG": "The default profile cannot be renamed", + "USAGE_TITLE": "Usage Instructions", + "USAGE_DESC": "Click \"Copy Launch Option\" button, then paste it into your Steam game's launch options to enable frame generation.", + "USAGE_CONFIG_NOTE": "The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running.", + "CLIPBOARD_COPIED": "Copied to clipboard", + "CLIPBOARD_COPYING": "Copying...", + "CLIPBOARD_COPY_LAUNCH": "Copy Launch Option", + "CLIPBOARD_LSFG_FGMOD": "LSFG + DeckyFG" + } +}
\ No newline at end of file |
