From 58b69fe25e128d4feed1164615e3eaae3acb4dbc Mon Sep 17 00:00:00 2001 From: SARDONYX-sard <68905624+SARDONYX-sard@users.noreply.github.com> Date: Fri, 16 Feb 2024 06:14:42 +0900 Subject: [PATCH] feat(frontend): implement backup dialog --- .../src/components/buttons/backup_btn.tsx | 105 +++++++++---- frontend/src/components/editor/css_editor.tsx | 48 ++++++ frontend/src/components/editor/index.ts | 12 ++ frontend/src/components/editor/js_editor.tsx | 76 +++++++++ frontend/src/components/form.tsx | 2 +- .../src/components/notifications/dialog.tsx | 147 ++++++++++++++++++ .../src/components/notifications/index.ts | 1 + frontend/src/components/pages/settings.tsx | 109 +------------ frontend/src/hooks/dyn_style.ts | 9 +- frontend/src/tauri_cmd/backup.ts | 23 +-- frontend/src/tauri_cmd/converter.ts | 2 +- frontend/src/utils/local_storage_manager.ts | 91 +++++++++++ frontend/src/utils/selector.ts | 8 +- locales/en-US.json | 10 +- locales/ja-JP.json | 14 +- test/sample_scripts/custom_translation.js | 16 +- .../g_dar2oar_settings-sample.json | 17 +- 17 files changed, 524 insertions(+), 166 deletions(-) create mode 100644 frontend/src/components/editor/css_editor.tsx create mode 100644 frontend/src/components/editor/index.ts create mode 100644 frontend/src/components/editor/js_editor.tsx create mode 100644 frontend/src/components/notifications/dialog.tsx create mode 100644 frontend/src/utils/local_storage_manager.ts diff --git a/frontend/src/components/buttons/backup_btn.tsx b/frontend/src/components/buttons/backup_btn.tsx index 3ed49b3..6dfe2d1 100644 --- a/frontend/src/components/buttons/backup_btn.tsx +++ b/frontend/src/components/buttons/backup_btn.tsx @@ -1,65 +1,105 @@ import FileDownloadIcon from '@mui/icons-material/FileDownload'; import FileOpen from '@mui/icons-material/FileOpen'; -import { Button, type ButtonProps, Tooltip } from '@mui/material'; -import { type ReactNode } from 'react'; +import { Button, Tooltip } from '@mui/material'; +import { type ReactNode, useState } from 'react'; -import { notify } from '@/components/notifications'; +import { + LocalStorageDialog, + notify, + type DialogClickHandler, + type LocalStorageDialogProps, +} from '@/components/notifications'; import { useTranslation } from '@/hooks'; import { backup } from '@/tauri_cmd'; +import { type LocalCache, localStorageManager } from '@/utils/local_storage_manager'; type Props = { - buttonName: ReactNode; + buttonName: string; + /** Trigger to open dialog */ + onClick?: () => void; + startIcon: ReactNode; tooltipTitle: ReactNode; -} & ButtonProps; +} & LocalStorageDialogProps; -export const BackupButton = ({ buttonName, tooltipTitle, ...props }: Readonly) => ( - - - -); +export const BackupButton = ({ buttonName, startIcon, tooltipTitle, onClick, ...props }: Readonly) => { + return ( + <> + + + + + + ); +}; export const ImportBackupButton = () => { const { t } = useTranslation(); + const [settings, setSettings] = useState({}); + const [open, setOpen] = useState(false); const handleClick = async () => { + const newSettings = await backup.import(); try { - await backup.import(); + if (newSettings) { + setSettings(newSettings); + setOpen(true); + } } catch (e) { notify.error(`${e}`); } }; + const handleDialogClick: DialogClickHandler = (checkedKeys) => { + checkedKeys.forEach((key) => { + const value = settings[key]; + if (value) { + localStorage.setItem(key, value); + } + }); + + window.location.reload(); // To enable + }; + return ( } + title={t('backup-import-dialog-title')} + tooltipTitle={t('backup-import-tooltip')} /> ); }; export const ExportBackupButton = () => { const { t } = useTranslation(); + const [open, setOpen] = useState(false); - const handleClick = async () => { + const handleClick: DialogClickHandler = async (checkedKeys) => { try { - if (await backup.export()) { + const exportTarget = localStorageManager.getFromKeys(checkedKeys); + if (await backup.export(exportTarget)) { notify.success(t('backup-export-success')); + setOpen(false); } } catch (e) { notify.error(`${e}`); @@ -69,9 +109,14 @@ export const ExportBackupButton = () => { return ( setOpen(true)} + setOpen={setOpen} startIcon={} + title={t('backup-export-dialog-title')} + tooltipTitle={t('backup-export-tooltip')} /> ); }; diff --git a/frontend/src/components/editor/css_editor.tsx b/frontend/src/components/editor/css_editor.tsx new file mode 100644 index 0000000..c3756e3 --- /dev/null +++ b/frontend/src/components/editor/css_editor.tsx @@ -0,0 +1,48 @@ +import InputLabel from '@mui/material/InputLabel'; +import AceEditor from 'react-ace'; + +import { useTranslation } from '@/hooks'; +import { selectEditorMode } from '@/utils/selector'; + +export type CSSEditorProps = { + editorMode: string; + setPreset: (script: string) => void; + setStyle: (style: string) => void; + style: string; +}; + +export const CSSEditor = ({ editorMode, setPreset, setStyle, style }: CSSEditorProps) => { + const { t } = useTranslation(); + + return ( + <> + {t('custom-css-label')} + { + setStyle(value); + localStorage.setItem('customCSS', value); + setPreset('0'); + }} + fontSize={'1rem'} + height="300px" + mode="css" + theme="one_dark" + value={style} + setOptions={{ useWorker: false }} + placeholder="{ body: url('https://localhost' }" + name="Custom CSS" + enableBasicAutocompletion + enableLiveAutocompletion + enableSnippets + keyboardHandler={selectEditorMode(editorMode)} + highlightActiveLine + tabSize={2} + editorProps={{ $blockScrolling: true }} + /> + + ); +}; diff --git a/frontend/src/components/editor/index.ts b/frontend/src/components/editor/index.ts new file mode 100644 index 0000000..0ea170d --- /dev/null +++ b/frontend/src/components/editor/index.ts @@ -0,0 +1,12 @@ +// @index('./*', f => `export * from '${f.path}'`) +export * from './css_editor'; +export * from './js_editor'; + +// NOTE: These extensions must be loaded after importing AceEditor or they will error +import 'ace-builds/src-min-noconflict/ext-language_tools'; // For completion +import 'ace-builds/src-min-noconflict/keybinding-vim'; +import 'ace-builds/src-min-noconflict/mode-css'; +import 'ace-builds/src-min-noconflict/mode-javascript'; +import 'ace-builds/src-min-noconflict/snippets/css'; +import 'ace-builds/src-min-noconflict/snippets/javascript'; +import 'ace-builds/src-min-noconflict/theme-one_dark'; diff --git a/frontend/src/components/editor/js_editor.tsx b/frontend/src/components/editor/js_editor.tsx new file mode 100644 index 0000000..79a1bc3 --- /dev/null +++ b/frontend/src/components/editor/js_editor.tsx @@ -0,0 +1,76 @@ +import { Checkbox, FormControlLabel, Grid, Tooltip } from '@mui/material'; +import InputLabel from '@mui/material/InputLabel'; +import AceEditor from 'react-ace'; + +import { useInjectScript, useStorageState, useTranslation } from '@/hooks'; +import { selectEditorMode } from '@/utils/selector'; + +export type JSEditorProps = { + editorMode: string; + marginTop?: string; +}; +export const JSEditor = ({ editorMode, marginTop }: JSEditorProps) => { + const { t } = useTranslation(); + + const [script, setScript] = useInjectScript(); + const [runScript, setRunScript] = useStorageState('runScript', 'false'); + + return ( + <> + + + {t('custom-js-label')} + + + {t('custom-js-auto-run-tooltip')} +
+ {t('custom-js-auto-run-tooltip2')} + + } + > + setRunScript(runScript === 'true' ? 'false' : 'true')} + /> + } + label={t('custom-js-auto-run-label')} + /> +
+
+ + { + localStorage.setItem('customJS', value); + setScript(value); + }} + placeholder={`(()=> { + const p = document.createElement('p'); + p.innerText = 'Hello'; + document.body.appendChild(p); +)()`} + editorProps={{ $blockScrolling: true }} + enableBasicAutocompletion + enableLiveAutocompletion + enableSnippets + fontSize={'1rem'} + height="250px" + highlightActiveLine + keyboardHandler={selectEditorMode(editorMode)} + mode="javascript" + name="Custom JavaScript" + setOptions={{ useWorker: false }} + tabSize={2} + theme="one_dark" + value={script} + /> + + ); +}; diff --git a/frontend/src/components/form.tsx b/frontend/src/components/form.tsx index 1c39647..968e63d 100644 --- a/frontend/src/components/form.tsx +++ b/frontend/src/components/form.tsx @@ -44,7 +44,7 @@ const getInitialFormValues = (): FormProps => ({ mappingPath: localStorage.getItem('mappingPath') ?? '', mapping1personPath: localStorage.getItem('mapping1personPath') ?? '', loading: false as boolean, - logLevel: selectLogLevel(localStorage.getItem('logLevel') ?? 'error'), + logLevel: selectLogLevel(localStorage.getItem('logLevel')), runParallel: localStorage.getItem('runParallel') === 'true', hideDar: localStorage.getItem('hideDar') === 'true', showProgress: localStorage.getItem('showProgress') === 'true', diff --git a/frontend/src/components/notifications/dialog.tsx b/frontend/src/components/notifications/dialog.tsx new file mode 100644 index 0000000..125dcc1 --- /dev/null +++ b/frontend/src/components/notifications/dialog.tsx @@ -0,0 +1,147 @@ +import CloseIcon from '@mui/icons-material/Close'; +import { Button, Checkbox, Dialog, Divider, FormControlLabel } from '@mui/material'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import IconButton from '@mui/material/IconButton'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { type ReactNode, useState, type Dispatch, type SetStateAction } from 'react'; + +import { useTranslation } from '@/hooks'; +import { CacheKey, LocalCache, pubCacheKeys } from '@/utils/local_storage_manager'; + +export type DialogClickHandler = (checkedKeys: CacheKey[]) => void; +export type LocalStorageDialogProps = { + buttonName: string; + cacheItems: LocalCache; + /** Event when a button in the dialog is clicked */ + inDialogClick: DialogClickHandler; + open: boolean; + setOpen: Dispatch>; + title: ReactNode; +}; + +export const LocalStorageDialog = ({ + buttonName, + cacheItems, + inDialogClick, + open, + setOpen, + title, +}: Readonly) => { + const handleClose = () => setOpen(false); + + const { t } = useTranslation(); + const [isAllChecked, setIsAllChecked] = useState(false); + const [isPubAllChecked, setIsPubAllChecked] = useState(false); + const [checked, setChecked] = useState([]); + + const handleToggle = (selectedKey: CacheKey) => () => { + setChecked((prev) => { + // Remove + if (prev.includes(selectedKey)) { + return prev.filter((key) => key !== selectedKey); + } + // Add + return [...prev, selectedKey]; + }); + setIsAllChecked(false); + setIsPubAllChecked(false); + }; + + const handleAllCheck = () => { + setIsPubAllChecked(false); + setIsAllChecked((prev) => { + const newIsAll = !prev; + if (newIsAll) { + setChecked(Object.keys(cacheItems) as CacheKey[]); + } else { + setChecked([]); + } + return newIsAll; + }); + }; + + const handlePubAllCheck = () => { + setIsAllChecked(false); + setIsPubAllChecked((prev) => { + const newIsPub = !prev; + if (newIsPub) { + // Safety: `setState` copies arguments without changing them on its own + setChecked(pubCacheKeys as unknown as CacheKey[]); + } else { + setChecked([]); + } + return newIsPub; + }); + }; + + return ( + + {title} + theme.palette.grey[500], + }} + > + + + + + } + label={t('backup-dialog-all-checked-label')} + /> + } + label={t('backup-dialog-pub-checked-label')} + /> + + + + + {Object.keys(cacheItems).map((key_) => { + const key = key_ as CacheKey; + const value = cacheItems[key]; + const labelId = `checkbox-list-label-${key}`; + const oneObject: LocalCache = {}; + oneObject[key] = value; + + return ( + + + + + + + + + + ); + })} + + + + + + + + ); +}; diff --git a/frontend/src/components/notifications/index.ts b/frontend/src/components/notifications/index.ts index 89ee78a..009ab20 100644 --- a/frontend/src/components/notifications/index.ts +++ b/frontend/src/components/notifications/index.ts @@ -1,4 +1,5 @@ // @index('./*', f => `export * from '${f.path}'`) export * from './circular_with_label'; +export * from './dialog'; export * from './progress_bar'; export * from './notify'; diff --git a/frontend/src/components/pages/settings.tsx b/frontend/src/components/pages/settings.tsx index 277b83b..4352bd6 100644 --- a/frontend/src/components/pages/settings.tsx +++ b/frontend/src/components/pages/settings.tsx @@ -4,11 +4,10 @@ import TabContext from '@mui/lab/TabContext'; import TabList from '@mui/lab/TabList'; import TabPanel from '@mui/lab/TabPanel'; import { Box, Button, Grid } from '@mui/material'; -import InputLabel from '@mui/material/InputLabel'; import Tab from '@mui/material/Tab'; -import AceEditor from 'react-ace'; import { ImportBackupButton, ExportBackupButton, ImportLangButton } from '@/components/buttons'; +import { CSSEditor, type CSSEditorProps, JSEditor } from '@/components/editor'; import { NoticePositionList, SelectEditorMode, @@ -17,26 +16,18 @@ import { type SelectEditorProps, type StyleListProps, } from '@/components/lists'; -import { useDynStyle, useInjectScript, useLocale, useStorageState, useTranslation } from '@/hooks'; +import { useDynStyle, useLocale, useStorageState, useTranslation } from '@/hooks'; import { start } from '@/tauri_cmd'; import { selectEditorMode, type EditorMode } from '@/utils/selector'; import packageJson from '@/../../package.json'; -// NOTE: These extensions must be loaded after importing AceEditor or they will error -import 'ace-builds/src-min-noconflict/ext-language_tools'; // For completion -import 'ace-builds/src-min-noconflict/keybinding-vim'; -import 'ace-builds/src-min-noconflict/mode-css'; -import 'ace-builds/src-min-noconflict/mode-javascript'; -import 'ace-builds/src-min-noconflict/snippets/css'; -import 'ace-builds/src-min-noconflict/snippets/javascript'; -import 'ace-builds/src-min-noconflict/theme-one_dark'; - export default function Settings() { useLocale(); const [editorMode, setEditorMode] = useStorageState('editorMode', 'default'); const [preset, setPreset] = useStorageState('presetNumber', '0'); const [style, setStyle] = useDynStyle(); + const validEditorMode = selectEditorMode(editorMode); const setEditorKeyMode = (editorMode: EditorMode) => setEditorMode(editorMode ?? 'default'); return ( @@ -51,14 +42,13 @@ export default function Settings() { width: '100%', }} > - - - + + void; - setStyle: (style: string) => void; - style: string; -}; -const CSSEditor = ({ editorMode, setPreset, setStyle, style }: CSSEditorProps) => { - const { t } = useTranslation(); - - return ( - <> - {t('custom-css-label')} - { - setStyle(value); - localStorage.setItem('customCSS', value); - setPreset('0'); - }} - fontSize={'1rem'} - height="300px" - mode="css" - theme="one_dark" - value={style} - setOptions={{ useWorker: false }} - placeholder="{ body: url('https://localhost' }" - name="Custom CSS" - enableBasicAutocompletion - enableLiveAutocompletion - enableSnippets - keyboardHandler={selectEditorMode(editorMode)} - highlightActiveLine - tabSize={2} - editorProps={{ $blockScrolling: true }} - /> - - ); -}; - -type JSEditorProps = { - editorMode: string; -}; -const JSEditor = ({ editorMode }: JSEditorProps) => { - const { t } = useTranslation(); - const [script, setScript] = useInjectScript(); - - return ( - <> - - {t('custom-js-label')} - - { - localStorage.setItem('customJS', value); - setScript(value); - }} - placeholder={`(()=> { - const p = document.createElement('p'); - p.innerText = 'Hello'; - document.body.appendChild(p); -)()`} - editorProps={{ $blockScrolling: true }} - enableBasicAutocompletion - enableLiveAutocompletion - enableSnippets - fontSize={'1rem'} - height="250px" - highlightActiveLine - keyboardHandler={selectEditorMode(editorMode)} - mode="javascript" - name="Custom JavaScript" - setOptions={{ useWorker: false }} - tabSize={2} - theme="one_dark" - value={script} - /> - - ); -}; - type TabsProps = StyleListProps & SelectEditorProps & CSSEditorProps; const Tabs = ({ editorMode, setEditorMode, preset, setPreset, setStyle }: TabsProps) => { const [value, setValue] = useStorageState('settings-tab-select', 'editor'); diff --git a/frontend/src/hooks/dyn_style.ts b/frontend/src/hooks/dyn_style.ts index 1513fc5..a7c064e 100644 --- a/frontend/src/hooks/dyn_style.ts +++ b/frontend/src/hooks/dyn_style.ts @@ -34,16 +34,21 @@ export function useDynStyle(initialState = getStyle()) { return [style, setStyle] as const; } +const initScript = () => { + return localStorage.getItem('customJS') ?? ''; +}; /** * Inject JavaScript */ -export function useInjectScript(initialState = (() => localStorage.getItem('customJS') ?? '')()) { +export function useInjectScript(initialState = initScript()) { const [script, setScript] = useState(initialState); const [pathname, setPathname] = useState(null); useEffect(() => { const scriptElement = document.createElement('script'); - scriptElement.innerHTML = script; + if (localStorage.getItem('runScript') === 'true') { + scriptElement.innerHTML = script; + } scriptElement.id = 'custom-script'; if (pathname !== window.location.pathname) { diff --git a/frontend/src/tauri_cmd/backup.ts b/frontend/src/tauri_cmd/backup.ts index 85a984b..278be7e 100644 --- a/frontend/src/tauri_cmd/backup.ts +++ b/frontend/src/tauri_cmd/backup.ts @@ -1,6 +1,7 @@ import { save } from '@tauri-apps/api/dialog'; -import { readFile, writeFile } from '.'; +import { readFile, writeFile } from '@/tauri_cmd'; +import { cacheKeys, type LocalCache } from '@/utils/local_storage_manager'; export const backup = { /** @throws Error */ @@ -8,21 +9,26 @@ export const backup = { const pathKey = 'import-backup-path'; const settings = await readFile(pathKey, 'g_dar2oar_settings'); if (settings) { - // TODO: This is unsafe because the key is not validated. const obj = JSON.parse(settings); + + // Validate Object.keys(obj).forEach((key) => { // The import path does not need to be overwritten. if (key === pathKey) { return; } - localStorage.setItem(key, obj[key]); + // Remove invalid settings values + if (!cacheKeys.includes(key as any)) { + delete obj[key]; + } }); - window.location.reload(); // To enable + + return obj as LocalCache; } }, /** @throws Error */ - async export() { + async export(settings: LocalCache) { const pathKey = 'export-settings-path'; const cachedPath = localStorage.getItem(pathKey); const path = await save({ @@ -36,12 +42,7 @@ export const backup = { }); if (typeof path === 'string') { - if (cachedPath === '') { - localStorage.removeItem(pathKey); - } else { - localStorage.setItem(pathKey, path); - } - await writeFile(path, JSON.stringify(localStorage, null, 2)); + await writeFile(path, JSON.stringify(settings, null, 2)); return path; } else { return null; diff --git a/frontend/src/tauri_cmd/converter.ts b/frontend/src/tauri_cmd/converter.ts index 1b57148..8de2cdf 100644 --- a/frontend/src/tauri_cmd/converter.ts +++ b/frontend/src/tauri_cmd/converter.ts @@ -40,7 +40,7 @@ export async function convertDar2oar(props: ConverterOptions): Promise { }, }; - let logLevel = selectLogLevel(localStorage.getItem('logLevel') ?? ''); + let logLevel = selectLogLevel(localStorage.getItem('logLevel')); changeLogLevel(logLevel); const showProgress = props.showProgress ?? false; diff --git a/frontend/src/utils/local_storage_manager.ts b/frontend/src/utils/local_storage_manager.ts new file mode 100644 index 0000000..b5f3ffc --- /dev/null +++ b/frontend/src/utils/local_storage_manager.ts @@ -0,0 +1,91 @@ +export const pubCacheKeys = [ + 'hideDar', + 'logLevel', + 'runParallel', + 'showProgress', + + 'custom-translation-dict', + 'customCSS', + 'customJS', + 'editorMode', + 'locale', + 'presetNumber', + 'settings-tab-select', + 'snackbar-position', +] as const; + +export const privateCacheKeys = [ + 'cached-dst', + 'cached-mapping1personPath', + 'cached-mappingPath', + 'cached-src', + 'dst', + 'mapping1personPath', + 'mappingPath', + 'modAuthor', + 'modName', + 'src', + + 'import-backup-path', + 'import-settings-path', + 'lang-file-path', +] as const; + +export const cacheKeys = [...pubCacheKeys, ...privateCacheKeys]; + +export type CacheKey = (typeof pubCacheKeys)[number] | (typeof privateCacheKeys)[number]; +export type LocalCache = Partial<{ + [key in CacheKey]: string; +}>; + +export const localStorageManager = { + /** + * @returns + * - Value associated with the given key + * - `null` if the given key does not exist. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Storage/getItem) + */ + get(key: CacheKey) { + return localStorage.getItem(key); + }, + getFromKeys(keys: CacheKey[]) { + const res: LocalCache = {}; + keys.forEach((key) => { + const value = localStorageManager.get(key); + if (value) { + res[key] = value; + } + }); + return res; + }, + getPubValues() { + const res: LocalCache = {}; + pubCacheKeys.forEach((key) => { + const val = localStorage.getItem(key); + if (val) { + res[key] = val; + } + }); + return res; + }, + getAll() { + const res: LocalCache = {}; + cacheKeys.forEach((key) => { + const val = localStorage.getItem(key); + if (val) { + res[key] = val; + } + }); + return res; + }, + /** Set cache */ + set(key: CacheKey, value: string) { + return localStorage.setItem(key, value); + }, + /** Remove cache */ + remove: (key: CacheKey) => localStorage.removeItem(key), + removePrivateItems() { + privateCacheKeys.forEach((key) => localStorage.removeItem(key)); + }, +}; diff --git a/frontend/src/utils/selector.ts b/frontend/src/utils/selector.ts index fd2db9e..7f458b3 100644 --- a/frontend/src/utils/selector.ts +++ b/frontend/src/utils/selector.ts @@ -1,7 +1,8 @@ import { LogLevel } from '@/tauri_cmd'; -export type EditorMode = 'default' | 'vim' | undefined; -export function selectEditorMode(select: string): EditorMode { +export type EditorMode = 'default' | 'vim'; +/** 'default' if null or undefined */ +export function selectEditorMode(select?: string | null): EditorMode { if (select === 'vim') { return select; } else { @@ -9,7 +10,8 @@ export function selectEditorMode(select: string): EditorMode { } } -export function selectLogLevel(logLevel: string): LogLevel { +/** 'error' if null or undefined */ +export function selectLogLevel(logLevel?: string | null): LogLevel { switch (logLevel) { case 'trace': case 'debug': diff --git a/locales/en-US.json b/locales/en-US.json index 98ff71e..c4f90ea 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,10 +1,15 @@ { "all-clear-btn": "All Clear", + "backup-dialog-all-checked-label": "Check all", + "backup-dialog-pub-checked-label": "Check all public", "backup-export-btn-name": "Export", - "backup-export-tooltip": "Export current settings.(Please be careful to handle personal information when transferring to others.)", + "backup-export-dialog-title": "Export Settings", "backup-export-success": "Settings exported.", + "backup-export-tooltip": "Export current settings.(Please be careful to handle personal information when transferring to others.)", "backup-import-btn-name": "Import", + "backup-import-dialog-title": "Import Settings", "backup-import-tooltip": "Import settings from Json file.(JavaScript is also executed at the moment of import! If it is someone else's file, please be careful that the JavaScript is not malicious.)", + "cancel-btn": "Cancel", "conversion-complete": "Conversion Complete.", "convert-btn": "Convert", "convert-form-author-name": "Mod Author Name", @@ -34,6 +39,9 @@ "css-preset-list-tooltip": "You can choose a CSS preset.", "css-preset-list-tooltip2": "Note: Editing \"Preset\" will overwrite \"Custom\".", "custom-css-label": "Currently applied CSS", + "custom-js-auto-run-label": "Auto run", + "custom-js-auto-run-tooltip": "Automatically run JavaScript on every page transition. You can change the UI and behavior, but use with caution.", + "custom-js-auto-run-tooltip2": "This configuration item will not be activated unless manually selected by the user.", "custom-js-label": "Custom JavaScript(Please do not execute untrusted scripts)", "editor-mode-list-label": "Editor Mode", "hide-dar-btn": "Hide DAR", diff --git a/locales/ja-JP.json b/locales/ja-JP.json index 4cc5926..e00a3fb 100644 --- a/locales/ja-JP.json +++ b/locales/ja-JP.json @@ -1,10 +1,15 @@ { "all-clear-btn": "全入力をクリア", + "backup-dialog-all-checked-label": "全選択", + "backup-dialog-pub-checked-label": "個人情報以外選択", "backup-export-btn-name": "エクスポート", - "backup-export-tooltip": "現在の設定をエクスポートします(他人に譲渡するときは個人情報の取り扱いに注意してください)", + "backup-export-dialog-title": "設定のエクスポート", "backup-export-success": "設定をエクスポートしました", + "backup-export-tooltip": "現在の設定をエクスポートします(他人に譲渡するときは個人情報の取り扱いに注意してください)", "backup-import-btn-name": "インポート", - "backup-import-tooltip": "Jsonファイルから設定をインポートします(インポートした瞬間JavaScriptも実行されます! 他人のファイルの場合はJavaScriptに悪意がないか注意してください。)", + "backup-import-dialog-title": "設定のインポート", + "backup-import-tooltip": "Jsonファイルから設定をインポートします(他人のファイルの場合はJavaScriptに悪意がないか注意してください。)", + "cancel-btn": "キャンセル", "conversion-complete": "変換が完了しました", "convert-btn": "変換", "convert-form-author-name": "Mod作者名", @@ -34,6 +39,9 @@ "css-preset-list-tooltip": "CSS presetを選択", "css-preset-list-tooltip2": "注意: 「プリセット」を編集すると「カスタム」が上書きされます", "custom-css-label": "現在適用されているCSS", + "custom-js-auto-run-label": "自動実行", + "custom-js-auto-run-tooltip": "ページを遷移するたびにJavaScriptを自動実行します。UIや挙動を変えられますが十分に注意してご使用ください。", + "custom-js-auto-run-tooltip2": "この設定項目はユーザーが手動で選択しない限り有効化されることはありません", "custom-js-label": "カスタムJavaScript(信用できないスクリプトは実行しないでください)", "editor-mode-list-label": "エディタモード", "hide-dar-btn": "DARを非表示", @@ -69,7 +77,7 @@ "remove-oar-failed": "「OpenAnimationReplacer」ディレクトリが見つかりません", "remove-oar-specify-error": "DARまたはOARが入力されていません", "remove-oar-success": "OARディレクトリを削除しました", - "remove-oar-tooltip": "OAR(出力先)」(未指定なら「DAR(入力)」)からOARディレクトリを探して削除します", + "remove-oar-tooltip": "「OAR(出力先)」、未指定なら「DAR(入力)」からOARディレクトリを探して削除します", "run-parallel-btn-tooltip": "マルチスレッドを使用します", "run-parallel-btn-tooltip2": "注意: 2倍以上の処理速度が期待できますが、並行処理によりスレッドの終了タイミングが順不同になるため、ログの書き込みも同様に順不同になりログの可読性が大幅に低下します。", "run-parallel-label": "並列実行", diff --git a/test/sample_scripts/custom_translation.js b/test/sample_scripts/custom_translation.js index 3c81f81..34ac161 100644 --- a/test/sample_scripts/custom_translation.js +++ b/test/sample_scripts/custom_translation.js @@ -2,11 +2,18 @@ // Origin: https://github.com/SARDONYX-sard/dar-to-oar/blob/main/locales/en-US.json const i18n = { 'all-clear-btn': 'All Clear', + 'backup-dialog-all-checked-label': 'Check all', + 'backup-dialog-pub-checked-label': 'Check all public', 'backup-export-btn-name': 'Export', - 'backup-export-tooltip': 'Export current settings', + 'backup-export-dialog-title': 'Export Settings', 'backup-export-success': 'Settings exported.', + 'backup-export-tooltip': + 'Export current settings.(Please be careful to handle personal information when transferring to others.)', 'backup-import-btn-name': 'Import', - 'backup-import-tooltip': 'Import settings from Json file.', + 'backup-import-dialog-title': 'Import Settings', + 'backup-import-tooltip': + "Import settings from Json file.(JavaScript is also executed at the moment of import! If it is someone else's file, please be careful that the JavaScript is not malicious.)", + 'cancel-btn': 'Cancel', 'conversion-complete': 'Conversion Complete.', 'convert-btn': 'Convert', 'convert-form-author-name': 'Mod Author Name', @@ -37,6 +44,11 @@ 'css-preset-list-tooltip': 'You can choose a CSS preset.', 'css-preset-list-tooltip2': 'Note: Editing "Preset" will overwrite "Custom".', 'custom-css-label': 'Currently applied CSS', + 'custom-js-auto-run-label': 'Auto run', + 'custom-js-auto-run-tooltip': + 'Automatically run JavaScript on every page transition. You can change the UI and behavior, but use with caution.', + 'custom-js-auto-run-tooltip2': + 'This configuration item will not be activated unless manually selected by the user.', 'custom-js-label': 'Custom JavaScript(Please do not execute untrusted scripts)', 'editor-mode-list-label': 'Editor Mode', 'hide-dar-btn': 'Hide DAR', diff --git a/test/sample_scripts/g_dar2oar_settings-sample.json b/test/sample_scripts/g_dar2oar_settings-sample.json index d697eea..6405153 100644 --- a/test/sample_scripts/g_dar2oar_settings-sample.json +++ b/test/sample_scripts/g_dar2oar_settings-sample.json @@ -1,14 +1,13 @@ { + "hideDar": "false", "logLevel": "debug", - "presetNumber": "0", + "runParallel": "true", "showProgress": "true", - "snackbar-position": "{\"vertical\":\"top\",\"horizontal\":\"left\"}", - "settings-tab-select": "backup", - "customCSS": "", + "custom-translation-dict": "{\"all-clear-btn\":\"清除所有输入\",\"backup-export-btn-name\":\"导出\",\"backup-export-tooltip\":\"导出当前设置\",\"backup-export-success\":\"成功导出设置\",\"backup-import-btn-name\":\"导入\",\"backup-import-tooltip\":\"从Json文件导入设置\",\"conversion-complete\":\"转换完成\",\"convert-btn\":\"转换\",\"convert-form-author-name\":\"Mod作者名称\",\"convert-form-author-name-helper\":\"[可选]\",\"convert-form-author-placeholder\":\"作者名称\",\"convert-form-dar-helper\":\"[必填] 包含\\\"DynamicAnimationReplacer\\\"的目录\",\"convert-form-dar-helper2\":\"\\\"C:\\\\[...]/Mod Name/\\\" -> 转换第一人称和第三人称\",\"convert-form-dar-helper3\":\"\\\"[...]/animations/DynamicAnimationReplacer\\\" -> 仅转换第三人称\",\"convert-form-dar-label\":\"DAR(输入)目录\",\"convert-form-mapping-1st-label\":\"映射表路径(第一人称)\",\"convert-form-mapping-help-link-name\":\"什么是映射文件?\",\"convert-form-mapping-helper\":\"[可选] 指定包含优先级编号和节名称对应关系的文件\",\"convert-form-mapping-helper2\":\"帮助: \",\"convert-form-mapping-label\":\"映射表路径\",\"convert-form-mod-name\":\"Mod名称\",\"convert-form-mod-name-helper\":\"[可选] 推荐使用ASCII(英文)\",\"convert-form-oar-helper\":\"[可选] 指定OAR的输出位置(例如: \\\"NewMod\\\" -> \\\"NewMod/meshes/[...]\\\")\",\"convert-form-oar-helper2\":\"如果未指定, 则OAR将在DAR相同级别的位置创建。\",\"convert-form-oar-label\":\"OAR(输出)目录\",\"converting-btn\":\"转换中...\",\"css-preset-list-item0\":\"自定义\",\"css-preset-list-item1\":\"预设1\",\"css-preset-list-item2\":\"预设2\",\"css-preset-list-item3\":\"预设3\",\"css-preset-list-item4\":\"预设4\",\"css-preset-list-label\":\"CSS预设\",\"css-preset-list-tooltip\":\"选择CSS预设\",\"css-preset-list-tooltip2\":\"注意: 编辑预设会覆盖\\\"自定义\\\"\",\"custom-css-label\":\"当前应用的CSS\",\"custom-js-label\":\"自定义JavaScript(请勿运行不可信脚本)\",\"editor-mode-list-label\":\"编辑器模式\",\"hide-dar-btn\":\"隐藏DAR\",\"hide-dar-btn-tooltip\":\"转换后, 将在所有DAR文件中添加“.mohidden”以隐藏(MO2用户专用)\",\"hide-dar-btn-tooltip2\":\"注意: 如果未指定OAR的输出位置, 这将特别有用。\",\"import-lang-btn\":\"导入语言\",\"import-lang-tooltip\":\"从Json文件导入任何语言。(导入后会自动重新加载)\",\"import-lang-tooltip2\":\"注意: 如果Json无效, 将回退到英文。(请参阅Wiki了解Json格式)\",\"lang-preset-auto\":\"自动\",\"lang-preset-custom\":\"自定义\",\"lang-preset-label\":\"语言\",\"log-level-list-label\":\"日志级别\",\"log-level-list-tooltip\":\"较轻的日志级别包括更严重的日志级别。(错误 ⊂ 信息)\",\"log-level-list-tooltip2\":\"调试: 记录转换过程中的条件数据\",\"log-level-list-tooltip3\":\"信息: 记录转换时间\",\"log-level-list-tooltip4\":\"错误: 仅记录严重错误\",\"mapping-wiki-url-leaf\":\"wiki#what-is-the-mapping-file\",\"notice-position-bottom-center\":\"底部中央\",\"notice-position-bottom-left\":\"底部左侧\",\"notice-position-bottom-right\":\"底部右侧\",\"notice-position-list-label\":\"通知位置\",\"notice-position-top-center\":\"顶部中央\",\"notice-position-top-left\":\"顶部左侧\",\"notice-position-top-right\":\"顶部右侧\",\"open-log-btn\":\"查看日志\",\"open-log-dir-btn\":\"打开日志目录\",\"open-log-dir-tooltip\":\"打开日志存储位置。\",\"open-log-tooltip\":\"打开当前日志文件。(每次启动应用都会旋转到新的日志文件)\",\"progress-btn\":\"进度条\",\"progress-btn-tooltip\":\"显示详细进度信息\",\"progress-btn-tooltip2\":\"\",\"remove-oar-btn\":\"删除OAR\",\"remove-oar-failed\":\"未找到\\\"OpenAnimationReplacer\\\"目录\",\"remove-oar-specify-error\":\"未输入DAR或OAR\",\"remove-oar-success\":\"成功删除OAR目录\",\"remove-oar-tooltip\":\"从\\\"OAR(输出)\\\"(如果未指定, 则从\\\"DAR(输入)\\\")中查找并删除OAR目录\",\"run-parallel-btn-tooltip\":\"使用多线程\",\"run-parallel-btn-tooltip2\":\"注意: 可以提高处理速度超过2倍, 但并行处理可能导致线程结束顺序不确定, 从而日志写入也是无序的, 降低了日志可读性。\",\"run-parallel-label\":\"并行执行\",\"select-btn\":\"选择\",\"tab-label-backup\":\"备份\",\"tab-label-editor\":\"编辑器与预设\",\"tab-label-lang\":\"语言\",\"tab-label-notice\":\"通知\",\"unhide-dar-btn\":\"取消隐藏DAR\",\"unhide-dar-btn-tooltip\":\"取消由\\\"隐藏DAR\\\"导致的隐藏(MO2用户专用)\",\"unhide-dar-failed\":\"未找到带有扩展名“.mohidden”的文件\",\"unhide-dar-specify-error\":\"请指定DAR(src)\",\"unhide-dar-success\":\"成功取消DAR的隐藏\"}", + "customJS": "(() => {\r\n // Change the background on each page.\r\n dynImg(\r\n /** index page */\r\n 'https://images.pexels.com/photos/4589833/pexels-photo-4589833.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',\r\n /** settings page */\r\n 'https://images.pexels.com/photos/7325003/pexels-photo-7325003.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',\r\n );\r\n\r\n // - Custom Transaction\r\n // How to apply?\r\n // 1. Use comment-out toggle key: Ctrl + /\r\n // 2. Apply by twice reload: (Ctrl + Shift + r) * 2\r\n // setCustomTranslation();\r\n // clearCustomTranslation();\r\n\r\n\r\n /**\r\n * Change the background on each page.\r\n * @param {string} indexUrl - Image URI of index(converter) page\r\n * @param {string} settingsUrl - Image URI of settings page\r\n */\r\n function dynImg(indexUrl, settingsUrl) {\r\n const common = `body {\r\n background-attachment: fixed;\r\n background-color: #000;\r\n background-image: var(--image-url);\r\n background-position-x: var(--image-position-x);\r\n background-position-y: var(--image-position-y);\r\n background-repeat: no-repeat;\r\n background-size: var(--image-size);\r\n}\r\nmain {\r\n background-color: var(--main-bg-color);\r\n}`\r\n \r\n const style = document.getElementById('dyn-style') ?? document.createElement('style');\r\n style.id = 'dyn-style';\r\n const currentPage = window.location.pathname;\r\n if (currentPage === '/') {\r\n style.innerHTML = `:root {\r\n --image-position-x: center;\r\n --image-position-y: center;\r\n --image-size: cover;\r\n --image-url: url('${indexUrl}'); \r\n --main-bg-color: #222a;\r\n } ${common}`;\r\n } else if (currentPage === '/settings') {\r\n style.innerHTML = `:root {\r\n --image-position-x: center;\r\n --image-position-y: bottom;\r\n --image-size: cover;\r\n --image-url: url('${settingsUrl}');\r\n --main-bg-color: #222a;\r\n } ${common}`;\r\n }\r\n document.body.appendChild(style);\r\n }\r\n\r\n function setCustomTranslation() {\r\n // Origin: https://github.com/SARDONYX-sard/dar-to-oar/blob/main/locales/en-US.json\r\n const i18n = {\r\n 'all-clear-btn': '清除所有输入',\r\n 'backup-export-btn-name': '导出',\r\n 'backup-export-tooltip': '导出当前设置',\r\n 'backup-export-success': '成功导出设置',\r\n 'backup-import-btn-name': '导入',\r\n 'backup-import-tooltip': '从Json文件导入设置',\r\n 'conversion-complete': '转换完成',\r\n 'convert-btn': '转换',\r\n 'convert-form-author-name': 'Mod作者名称',\r\n 'convert-form-author-name-helper': '[可选]',\r\n 'convert-form-author-placeholder': '作者名称',\r\n 'convert-form-dar-helper': '[必填] 包含\"DynamicAnimationReplacer\"的目录',\r\n 'convert-form-dar-helper2': '\"C:\\\\[...]/Mod Name/\" -> 转换第一人称和第三人称',\r\n 'convert-form-dar-helper3': '\"[...]/animations/DynamicAnimationReplacer\" -> 仅转换第三人称',\r\n 'convert-form-dar-label': 'DAR(输入)目录',\r\n 'convert-form-mapping-1st-label': '映射表路径(第一人称)',\r\n 'convert-form-mapping-help-link-name': '什么是映射文件?',\r\n 'convert-form-mapping-helper': '[可选] 指定包含优先级编号和节名称对应关系的文件',\r\n 'convert-form-mapping-helper2': '帮助: ',\r\n 'convert-form-mapping-label': '映射表路径',\r\n 'convert-form-mod-name': 'Mod名称',\r\n 'convert-form-mod-name-helper': '[可选] 推荐使用ASCII(英文)',\r\n 'convert-form-oar-helper': '[可选] 指定OAR的输出位置(例如: \"NewMod\" -> \"NewMod/meshes/[...]\")',\r\n 'convert-form-oar-helper2': '如果未指定, 则OAR将在DAR相同级别的位置创建。',\r\n 'convert-form-oar-label': 'OAR(输出)目录',\r\n 'converting-btn': '转换中...',\r\n 'css-preset-list-item0': '自定义',\r\n 'css-preset-list-item1': '预设1',\r\n 'css-preset-list-item2': '预设2',\r\n 'css-preset-list-item3': '预设3',\r\n 'css-preset-list-item4': '预设4',\r\n 'css-preset-list-label': 'CSS预设',\r\n 'css-preset-list-tooltip': '选择CSS预设',\r\n 'css-preset-list-tooltip2': '注意: 编辑预设会覆盖\"自定义\"',\r\n 'custom-css-label': '当前应用的CSS',\r\n 'custom-js-label': '自定义JavaScript(请勿运行不可信脚本)',\r\n 'editor-mode-list-label': '编辑器模式',\r\n 'hide-dar-btn': '隐藏DAR',\r\n 'hide-dar-btn-tooltip': '转换后, 将在所有DAR文件中添加“.mohidden”以隐藏(MO2用户专用)',\r\n 'hide-dar-btn-tooltip2': '注意: 如果未指定OAR的输出位置, 这将特别有用。',\r\n 'import-lang-btn': '导入语言',\r\n 'import-lang-tooltip': '从Json文件导入任何语言。(导入后会自动重新加载)',\r\n 'import-lang-tooltip2': '注意: 如果Json无效, 将回退到英文。(请参阅Wiki了解Json格式)',\r\n 'lang-preset-auto': '自动',\r\n 'lang-preset-custom': '自定义',\r\n 'lang-preset-label': '语言',\r\n 'log-level-list-label': '日志级别',\r\n 'log-level-list-tooltip': '较轻的日志级别包括更严重的日志级别。(错误 ⊂ 信息)',\r\n 'log-level-list-tooltip2': '调试: 记录转换过程中的条件数据',\r\n 'log-level-list-tooltip3': '信息: 记录转换时间',\r\n 'log-level-list-tooltip4': '错误: 仅记录严重错误',\r\n 'mapping-wiki-url-leaf': 'wiki#what-is-the-mapping-file',\r\n 'notice-position-bottom-center': '底部中央',\r\n 'notice-position-bottom-left': '底部左侧',\r\n 'notice-position-bottom-right': '底部右侧',\r\n 'notice-position-list-label': '通知位置',\r\n 'notice-position-top-center': '顶部中央',\r\n 'notice-position-top-left': '顶部左侧',\r\n 'notice-position-top-right': '顶部右侧',\r\n 'open-log-btn': '查看日志',\r\n 'open-log-dir-btn': '打开日志目录',\r\n 'open-log-dir-tooltip': '打开日志存储位置。',\r\n 'open-log-tooltip': '打开当前日志文件。(每次启动应用都会旋转到新的日志文件)',\r\n 'progress-btn': '进度条',\r\n 'progress-btn-tooltip': '显示详细进度信息',\r\n 'progress-btn-tooltip2': '',\r\n 'remove-oar-btn': '删除OAR',\r\n 'remove-oar-failed': '未找到\"OpenAnimationReplacer\"目录',\r\n 'remove-oar-specify-error': '未输入DAR或OAR',\r\n 'remove-oar-success': '成功删除OAR目录',\r\n 'remove-oar-tooltip': '从\"OAR(输出)\"(如果未指定, 则从\"DAR(输入)\")中查找并删除OAR目录',\r\n 'run-parallel-btn-tooltip': '使用多线程',\r\n 'run-parallel-btn-tooltip2':\r\n '注意: 可以提高处理速度超过2倍, 但并行处理可能导致线程结束顺序不确定, 从而日志写入也是无序的, 降低了日志可读性。',\r\n 'run-parallel-label': '并行执行',\r\n 'select-btn': '选择',\r\n 'tab-label-backup': '备份',\r\n 'tab-label-editor': '编辑器与预设',\r\n 'tab-label-lang': '语言',\r\n 'tab-label-notice': '通知',\r\n 'unhide-dar-btn': '取消隐藏DAR',\r\n 'unhide-dar-btn-tooltip': '取消由\"隐藏DAR\"导致的隐藏(MO2用户专用)',\r\n 'unhide-dar-failed': '未找到带有扩展名“.mohidden”的文件',\r\n 'unhide-dar-specify-error': '请指定DAR(src)',\r\n 'unhide-dar-success': '成功取消DAR的隐藏',\r\n };\r\n localStorage.setItem('custom-translation-dict', JSON.stringify(i18n));\r\n localStorage.setItem('locale', 'custom');\r\n }\r\n \r\n function clearCustomTranslation() {\r\n localStorage.removeItem('custom-translation-dict');\r\n }\r\n})();\r\n", "editorMode": "vim", "locale": "auto", - "customJS": "(() => {\r\n // Change the background on each page.\r\n dynImg(\r\n /** index page */\r\n 'https://images.pexels.com/photos/4589833/pexels-photo-4589833.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',\r\n /** settings page */\r\n 'https://images.pexels.com/photos/7325003/pexels-photo-7325003.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',\r\n );\r\n // - Run before Export to erase personal information.(For transfer to others))\r\n // - Available for checks: Press F12 -> Application -> Local storage\r\n // clearPrivateCache();\r\n\r\n // - Custom Transaction\r\n // How to apply?\r\n // 1. Use comment-out toggle key: Ctrl + /\r\n // 2. Apply by twice reload: (Ctrl + Shift + r) * 2\r\n // setCustomTranslation();\r\n // clearCustomTranslation();\r\n\r\n \r\n /**\r\n * Change the background on each page.\r\n * @param {string} indexUrl - Image URI of index(converter) page\r\n * @param {string} settingsUrl - Image URI of settings page\r\n */\r\n function dynImg(indexUrl, settingsUrl) {\r\n const common = `body {\r\n background-attachment: fixed;\r\n background-color: #000;\r\n background-image: var(--image-url);\r\n background-position-x: var(--image-position-x);\r\n background-position-y: var(--image-position-y);\r\n background-repeat: no-repeat;\r\n background-size: var(--image-size);\r\n}\r\nmain {\r\n background-color: var(--main-bg-color);\r\n}`\r\n \r\n const style = document.getElementById('dyn-style') ?? document.createElement('style');\r\n style.id = 'dyn-style';\r\n const currentPage = window.location.pathname;\r\n if (currentPage === '/') {\r\n style.innerHTML = `:root {\r\n --image-url: url('${indexUrl}'); \r\n --image-position-x: center;\r\n --image-position-y: bottom;\r\n --main-bg-color: #222a;\r\n } ${common}`;\r\n } else if (currentPage === '/settings') {\r\n style.innerHTML = `:root {\r\n --image-url: url('${settingsUrl}');\r\n --image-position-x: center;\r\n --image-position-y: bottom;\r\n --main-bg-color: #222a;\r\n } ${common}`;\r\n }\r\n document.body.appendChild(style);\r\n }\r\n\r\n /**\r\n * Delete personal information before Exporting settings.\r\n */\r\n function clearPrivateCache() {\r\n const removeTargets = [\r\n 'cached-dst',\r\n 'cached-mapping1personPath',\r\n 'cached-mappingPath',\r\n 'cached-src',\r\n 'dst',\r\n 'mapping1personPath',\r\n 'mappingPath',\r\n 'modAuthor',\r\n 'modName',\r\n 'src',\r\n // 'hideDar',\r\n // 'logLevel',\r\n // 'runParallel',\r\n // 'showProgress',\r\n\r\n // 'custom-translation-dict',\r\n // 'customCSS',\r\n // 'customJS',\r\n // 'editorMode',\r\n 'import-backup-path',\r\n 'import-settings-path',\r\n 'lang-file-path',\r\n // 'locale',\r\n // 'presetNumber',\r\n // 'settings-tab-select',\r\n // 'snackbar-position',\r\n ];\r\n Object.keys(localStorage).forEach((key) => {\r\n if (removeTargets.includes(key)) {\r\n localStorage.removeItem(key);\r\n } else if (key === 'export-settings-path') {\r\n // If you do not want to export only this key for implementation reasons, put an empty string ('').\r\n localStorage.setItem('export-settings-path', '')\r\n }\r\n });\r\n }\r\n\r\n function setCustomTranslation() {\r\n // Origin: https://github.com/SARDONYX-sard/dar-to-oar/blob/main/locales/en-US.json\r\n const i18n = {\r\n 'all-clear-btn': '清除所有输入',\r\n 'backup-export-btn-name': '导出',\r\n 'backup-export-tooltip': '导出当前设置',\r\n 'backup-export-success': '成功导出设置',\r\n 'backup-import-btn-name': '导入',\r\n 'backup-import-tooltip': '从Json文件导入设置',\r\n 'conversion-complete': '转换完成',\r\n 'convert-btn': '转换',\r\n 'convert-form-author-name': 'Mod作者名称',\r\n 'convert-form-author-name-helper': '[可选]',\r\n 'convert-form-author-placeholder': '作者名称',\r\n 'convert-form-dar-helper': '[必填] 包含\"DynamicAnimationReplacer\"的目录',\r\n 'convert-form-dar-helper2': '\"C:\\\\[...]/Mod Name/\" -> 转换第一人称和第三人称',\r\n 'convert-form-dar-helper3': '\"[...]/animations/DynamicAnimationReplacer\" -> 仅转换第三人称',\r\n 'convert-form-dar-label': 'DAR(输入)目录',\r\n 'convert-form-mapping-1st-label': '映射表路径(第一人称)',\r\n 'convert-form-mapping-help-link-name': '什么是映射文件?',\r\n 'convert-form-mapping-helper': '[可选] 指定包含优先级编号和节名称对应关系的文件',\r\n 'convert-form-mapping-helper2': '帮助: ',\r\n 'convert-form-mapping-label': '映射表路径',\r\n 'convert-form-mod-name': 'Mod名称',\r\n 'convert-form-mod-name-helper': '[可选] 推荐使用ASCII(英文)',\r\n 'convert-form-oar-helper': '[可选] 指定OAR的输出位置(例如: \"NewMod\" -> \"NewMod/meshes/[...]\")',\r\n 'convert-form-oar-helper2': '如果未指定, 则OAR将在DAR相同级别的位置创建。',\r\n 'convert-form-oar-label': 'OAR(输出)目录',\r\n 'converting-btn': '转换中...',\r\n 'css-preset-list-item0': '自定义',\r\n 'css-preset-list-item1': '预设1',\r\n 'css-preset-list-item2': '预设2',\r\n 'css-preset-list-item3': '预设3',\r\n 'css-preset-list-item4': '预设4',\r\n 'css-preset-list-label': 'CSS预设',\r\n 'css-preset-list-tooltip': '选择CSS预设',\r\n 'css-preset-list-tooltip2': '注意: 编辑预设会覆盖\"自定义\"',\r\n 'custom-css-label': '当前应用的CSS',\r\n 'custom-js-label': '自定义JavaScript(请勿运行不可信脚本)',\r\n 'editor-mode-list-label': '编辑器模式',\r\n 'hide-dar-btn': '隐藏DAR',\r\n 'hide-dar-btn-tooltip': '转换后, 将在所有DAR文件中添加“.mohidden”以隐藏(MO2用户专用)',\r\n 'hide-dar-btn-tooltip2': '注意: 如果未指定OAR的输出位置, 这将特别有用。',\r\n 'import-lang-btn': '导入语言',\r\n 'import-lang-tooltip': '从Json文件导入任何语言。(导入后会自动重新加载)',\r\n 'import-lang-tooltip2': '注意: 如果Json无效, 将回退到英文。(请参阅Wiki了解Json格式)',\r\n 'lang-preset-auto': '自动',\r\n 'lang-preset-custom': '自定义',\r\n 'lang-preset-label': '语言',\r\n 'log-level-list-label': '日志级别',\r\n 'log-level-list-tooltip': '较轻的日志级别包括更严重的日志级别。(错误 ⊂ 信息)',\r\n 'log-level-list-tooltip2': '调试: 记录转换过程中的条件数据',\r\n 'log-level-list-tooltip3': '信息: 记录转换时间',\r\n 'log-level-list-tooltip4': '错误: 仅记录严重错误',\r\n 'mapping-wiki-url-leaf': 'wiki#what-is-the-mapping-file',\r\n 'notice-position-bottom-center': '底部中央',\r\n 'notice-position-bottom-left': '底部左侧',\r\n 'notice-position-bottom-right': '底部右侧',\r\n 'notice-position-list-label': '通知位置',\r\n 'notice-position-top-center': '顶部中央',\r\n 'notice-position-top-left': '顶部左侧',\r\n 'notice-position-top-right': '顶部右侧',\r\n 'open-log-btn': '查看日志',\r\n 'open-log-dir-btn': '打开日志目录',\r\n 'open-log-dir-tooltip': '打开日志存储位置。',\r\n 'open-log-tooltip': '打开当前日志文件。(每次启动应用都会旋转到新的日志文件)',\r\n 'progress-btn': '进度条',\r\n 'progress-btn-tooltip': '显示详细进度信息',\r\n 'progress-btn-tooltip2': '',\r\n 'remove-oar-btn': '删除OAR',\r\n 'remove-oar-failed': '未找到\"OpenAnimationReplacer\"目录',\r\n 'remove-oar-specify-error': '未输入DAR或OAR',\r\n 'remove-oar-success': '成功删除OAR目录',\r\n 'remove-oar-tooltip': '从\"OAR(输出)\"(如果未指定, 则从\"DAR(输入)\")中查找并删除OAR目录',\r\n 'run-parallel-btn-tooltip': '使用多线程',\r\n 'run-parallel-btn-tooltip2':\r\n '注意: 可以提高处理速度超过2倍, 但并行处理可能导致线程结束顺序不确定, 从而日志写入也是无序的, 降低了日志可读性。',\r\n 'run-parallel-label': '并行执行',\r\n 'select-btn': '选择',\r\n 'tab-label-backup': '备份',\r\n 'tab-label-editor': '编辑器与预设',\r\n 'tab-label-lang': '语言',\r\n 'tab-label-notice': '通知',\r\n 'unhide-dar-btn': '取消隐藏DAR',\r\n 'unhide-dar-btn-tooltip': '取消由\"隐藏DAR\"导致的隐藏(MO2用户专用)',\r\n 'unhide-dar-failed': '未找到带有扩展名“.mohidden”的文件',\r\n 'unhide-dar-specify-error': '请指定DAR(src)',\r\n 'unhide-dar-success': '成功取消DAR的隐藏',\r\n };\r\n localStorage.setItem('custom-translation-dict', JSON.stringify(i18n));\r\n localStorage.setItem('locale', 'custom');\r\n }\r\n \r\n function clearCustomTranslation() {\r\n localStorage.removeItem('custom-translation-dict');\r\n }\r\n})();\r\n", - "hideDar": "false", - "custom-translation-dict": "{\"all-clear-btn\":\"清除所有输入\",\"backup-export-btn-name\":\"导出\",\"backup-export-tooltip\":\"导出当前设置\",\"backup-export-success\":\"成功导出设置\",\"backup-import-btn-name\":\"导入\",\"backup-import-tooltip\":\"从Json文件导入设置\",\"conversion-complete\":\"转换完成\",\"convert-btn\":\"转换\",\"convert-form-author-name\":\"Mod作者名称\",\"convert-form-author-name-helper\":\"[可选]\",\"convert-form-author-placeholder\":\"作者名称\",\"convert-form-dar-helper\":\"[必填] 包含\\\"DynamicAnimationReplacer\\\"的目录\",\"convert-form-dar-helper2\":\"\\\"C:\\\\[...]/Mod Name/\\\" -> 转换第一人称和第三人称\",\"convert-form-dar-helper3\":\"\\\"[...]/animations/DynamicAnimationReplacer\\\" -> 仅转换第三人称\",\"convert-form-dar-label\":\"DAR(输入)目录\",\"convert-form-mapping-1st-label\":\"映射表路径(第一人称)\",\"convert-form-mapping-help-link-name\":\"什么是映射文件?\",\"convert-form-mapping-helper\":\"[可选] 指定包含优先级编号和节名称对应关系的文件\",\"convert-form-mapping-helper2\":\"帮助: \",\"convert-form-mapping-label\":\"映射表路径\",\"convert-form-mod-name\":\"Mod名称\",\"convert-form-mod-name-helper\":\"[可选] 推荐使用ASCII(英文)\",\"convert-form-oar-helper\":\"[可选] 指定OAR的输出位置(例如: \\\"NewMod\\\" -> \\\"NewMod/meshes/[...]\\\")\",\"convert-form-oar-helper2\":\"如果未指定, 则OAR将在DAR相同级别的位置创建。\",\"convert-form-oar-label\":\"OAR(输出)目录\",\"converting-btn\":\"转换中...\",\"css-preset-list-item0\":\"自定义\",\"css-preset-list-item1\":\"预设1\",\"css-preset-list-item2\":\"预设2\",\"css-preset-list-item3\":\"预设3\",\"css-preset-list-item4\":\"预设4\",\"css-preset-list-label\":\"CSS预设\",\"css-preset-list-tooltip\":\"选择CSS预设\",\"css-preset-list-tooltip2\":\"注意: 编辑预设会覆盖\\\"自定义\\\"\",\"custom-css-label\":\"当前应用的CSS\",\"custom-js-label\":\"自定义JavaScript(请勿运行不可信脚本)\",\"editor-mode-list-label\":\"编辑器模式\",\"hide-dar-btn\":\"隐藏DAR\",\"hide-dar-btn-tooltip\":\"转换后, 将在所有DAR文件中添加“.mohidden”以隐藏(MO2用户专用)\",\"hide-dar-btn-tooltip2\":\"注意: 如果未指定OAR的输出位置, 这将特别有用。\",\"import-lang-btn\":\"导入语言\",\"import-lang-tooltip\":\"从Json文件导入任何语言。(导入后会自动重新加载)\",\"import-lang-tooltip2\":\"注意: 如果Json无效, 将回退到英文。(请参阅Wiki了解Json格式)\",\"lang-preset-auto\":\"自动\",\"lang-preset-custom\":\"自定义\",\"lang-preset-label\":\"语言\",\"log-level-list-label\":\"日志级别\",\"log-level-list-tooltip\":\"较轻的日志级别包括更严重的日志级别。(错误 ⊂ 信息)\",\"log-level-list-tooltip2\":\"调试: 记录转换过程中的条件数据\",\"log-level-list-tooltip3\":\"信息: 记录转换时间\",\"log-level-list-tooltip4\":\"错误: 仅记录严重错误\",\"mapping-wiki-url-leaf\":\"wiki#what-is-the-mapping-file\",\"notice-position-bottom-center\":\"底部中央\",\"notice-position-bottom-left\":\"底部左侧\",\"notice-position-bottom-right\":\"底部右侧\",\"notice-position-list-label\":\"通知位置\",\"notice-position-top-center\":\"顶部中央\",\"notice-position-top-left\":\"顶部左侧\",\"notice-position-top-right\":\"顶部右侧\",\"open-log-btn\":\"查看日志\",\"open-log-dir-btn\":\"打开日志目录\",\"open-log-dir-tooltip\":\"打开日志存储位置。\",\"open-log-tooltip\":\"打开当前日志文件。(每次启动应用都会旋转到新的日志文件)\",\"progress-btn\":\"进度条\",\"progress-btn-tooltip\":\"显示详细进度信息\",\"progress-btn-tooltip2\":\"\",\"remove-oar-btn\":\"删除OAR\",\"remove-oar-failed\":\"未找到\\\"OpenAnimationReplacer\\\"目录\",\"remove-oar-specify-error\":\"未输入DAR或OAR\",\"remove-oar-success\":\"成功删除OAR目录\",\"remove-oar-tooltip\":\"从\\\"OAR(输出)\\\"(如果未指定, 则从\\\"DAR(输入)\\\")中查找并删除OAR目录\",\"run-parallel-btn-tooltip\":\"使用多线程\",\"run-parallel-btn-tooltip2\":\"注意: 可以提高处理速度超过2倍, 但并行处理可能导致线程结束顺序不确定, 从而日志写入也是无序的, 降低了日志可读性。\",\"run-parallel-label\":\"并行执行\",\"select-btn\":\"选择\",\"tab-label-backup\":\"备份\",\"tab-label-editor\":\"编辑器与预设\",\"tab-label-lang\":\"语言\",\"tab-label-notice\":\"通知\",\"unhide-dar-btn\":\"取消隐藏DAR\",\"unhide-dar-btn-tooltip\":\"取消由\\\"隐藏DAR\\\"导致的隐藏(MO2用户专用)\",\"unhide-dar-failed\":\"未找到带有扩展名“.mohidden”的文件\",\"unhide-dar-specify-error\":\"请指定DAR(src)\",\"unhide-dar-success\":\"成功取消DAR的隐藏\"}", - "runParallel": "true" -} + "presetNumber": "0", + "settings-tab-select": "backup", + "snackbar-position": "{\"vertical\":\"top\",\"horizontal\":\"left\"}" +} \ No newline at end of file