diff --git a/apps/app-frontend/src/components/ui/instance_settings/SyncSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/SyncSettings.vue index 9032ecd..afd121f 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/SyncSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/SyncSettings.vue @@ -35,6 +35,7 @@ import CommandHistoryModal from '../settings/instances/command-history-modal.vue import SyncedServersModal from '../settings/instances/servers-modal.vue' import SyncedPacksModal from '../settings/instances/SyncedPacksModal.vue' import SyncSourceModal from '../settings/instances/SyncSourceModal.vue' +import GameSettingsModal from '../settings/instances/game-settings-modal/index.vue' const { handleError } = injectNotificationManager() const { formatMessage } = useVIntl() @@ -42,6 +43,7 @@ const queryClient = useQueryClient() const syncedPacksModal = ref>() const commandHistoryModal = ref>() const syncedServersModal = ref>() +const gameSettingsModal = ref | null>(null) const messages = defineMessages({ globalOptionsTitle: { @@ -513,8 +515,7 @@ function editGlobalOption(row: (typeof globalRows)[number]) { if (row.editable === 'resourcepack' || row.editable === 'datapack') { void syncedPacksModal.value?.show(row.editable) } else if (row.editable === 'game-settings') { - // Game settings modal is ported in a later pass. - return + openGameSettings() } else if (row.editable === 'commands') { void commandHistoryModal.value?.show() } else { @@ -522,6 +523,15 @@ function editGlobalOption(row: (typeof globalRows)[number]) { } } +function openGameSettings() { + if (!hasGameOptionsToEdit.value) return + gameSettingsModal.value?.show() +} + +async function handleGameSettingsSaved() { + await invalidateSyncedOptions() +} + function clearBaseSource() { @@ -537,6 +547,7 @@ onScopeDispose(clearBaseSource) + props.instance) const tabbedModal = ref | null>(null) -function hide() { +function hide(onAfterClose?: () => void) { tabbedModal.value?.hide() + if (onAfterClose) nextTick(onAfterClose) } provideInstanceSettings({ diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/boolean-control.vue b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/boolean-control.vue new file mode 100644 index 0000000..009aaff --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/boolean-control.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/editors.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/editors.ts new file mode 100644 index 0000000..16908ab --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/editors.ts @@ -0,0 +1,163 @@ +import { toRaw } from 'vue' + +import type { + EditableGameSetting, + GameOptionCanonicalValue, + GameSettingChange, + GameSettingsEditorState, +} from '@/helpers/game-options' + +function clonePlainValue(value: unknown): unknown { + if (Array.isArray(value)) { + return toRaw(value).map(clonePlainValue) + } + if (value !== null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(toRaw(value)).map(([key, nestedValue]) => [key, clonePlainValue(nestedValue)]), + ) + } + return value +} + +export function cloneGameSettingsState(state: GameSettingsEditorState): GameSettingsEditorState { + return clonePlainValue(state) as GameSettingsEditorState +} + +export function canonicalValuesEqual( + left: GameOptionCanonicalValue | null | undefined, + right: GameOptionCanonicalValue | null | undefined, +): boolean { + return JSON.stringify(left ?? null) === JSON.stringify(right ?? null) +} + +export function gameSettingChanges( + base: GameSettingsEditorState | null, + draft: GameSettingsEditorState | null, + forcedValueOptionIds: ReadonlySet = new Set(), +): GameSettingChange[] { + if (!base || !draft) return [] + + const baseSettings = new Map(base.settings.map((setting) => [setting.option_id, setting])) + return draft.settings.flatMap((setting) => { + const previous = baseSettings.get(setting.option_id) + if (!previous) return [] + + const syncChanged = previous.sync_enabled !== setting.sync_enabled + const valueChanged = !canonicalValuesEqual(previous.canonical_value, setting.canonical_value) + const promoteLocalValue = + forcedValueOptionIds.has(setting.option_id) && + previous.value_state !== 'canonical' && + setting.canonical_value !== null && + setting.canonical_value !== undefined + if (!syncChanged && !valueChanged && !promoteLocalValue) return [] + + return [ + { + option_id: setting.option_id, + base_option_revision: previous.option_revision, + ...(syncChanged ? { sync_enabled: setting.sync_enabled } : {}), + ...(valueChanged || promoteLocalValue + ? { canonical_value: setting.canonical_value ?? null } + : {}), + }, + ] + }) +} + +export function gameSettingNumberScale(setting: EditableGameSetting): number { + if (setting.editor.unit !== 'percent') return 1 + return setting.option_id === 'sensitivity' ? 200 : 100 +} + +export function canonicalValueText(setting: EditableGameSetting): string { + const value = setting.canonical_value + if (!value) return '' + + switch (value.type) { + case 'bool': + return value.value ? 'true' : 'false' + case 'integer': + case 'decimal': + return setting.editor.unit === 'percent' + ? String(Number((Number(value.value) * gameSettingNumberScale(setting)).toFixed(8))) + : String(value.value) + case 'string_list': + return value.value.join(', ') + default: + return value.value + } +} + +export function canonicalBooleanValue(setting: EditableGameSetting): boolean | undefined { + return setting.canonical_value?.type === 'bool' ? setting.canonical_value.value : undefined +} + +export function isKeybindSetting(setting: EditableGameSetting): boolean { + return ( + setting.editor.type === 'key_binding' || + (setting.editor.type === 'external_raw' && !!setting.raw_key?.startsWith('key_')) + ) +} + +export function canonicalValueFromInput( + setting: EditableGameSetting, + value: string | number | boolean | undefined, +): GameOptionCanonicalValue | null { + if (value === undefined || value === '') return null + + switch (setting.editor.type) { + case 'bool': + return { type: 'bool', value: Boolean(value) } + case 'integer': { + const parsed = Number(value) + return Number.isSafeInteger(parsed) ? { type: 'integer', value: parsed } : null + } + case 'decimal': { + const parsed = Number(value) + if (!Number.isFinite(parsed)) return null + return { + type: 'decimal', + value: + setting.editor.unit === 'percent' + ? String(Number((parsed / gameSettingNumberScale(setting)).toFixed(8))) + : String(value), + } + } + case 'enum': + return { type: 'enum', value: String(value) } + case 'string_list': + return { + type: 'string_list', + value: String(value) + .split(',') + .map((item) => item.trim()) + .filter(Boolean), + } + case 'key_binding': + return { type: 'key_binding', value: String(value) } + case 'external_raw': + return { type: 'external_raw', value: String(value) } + default: + return { type: 'text', value: String(value) } + } +} + +export function settingSearchText( + setting: EditableGameSetting, + label: string, + description: string, +): string { + return [label, description, setting.option_id, setting.raw_key] + .filter(Boolean) + .join(' ') + .toLocaleLowerCase() +} + +export function settingCanBeEnabled(setting: EditableGameSetting): boolean { + return ( + !setting.controlled && + !setting.validation_error && + !['mixed', 'unset', 'invalid'].includes(setting.value_state) && + (setting.compatibility.total_participating === 0 || setting.compatibility.will_receive > 0) + ) +} diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/index.vue b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/index.vue new file mode 100644 index 0000000..693c517 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/index.vue @@ -0,0 +1,483 @@ + + + diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybind-input.vue b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybind-input.vue new file mode 100644 index 0000000..aa673b3 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybind-input.vue @@ -0,0 +1,235 @@ + + + diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybinds.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybinds.ts new file mode 100644 index 0000000..ec64a32 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/keybinds.ts @@ -0,0 +1,426 @@ +import { defineMessages, type MessageDescriptor, type VIntlFormatters } from '@modrinth/ui' + +type FormatMessage = VIntlFormatters['formatMessage'] + +type StopKeybindRecording = () => void +let stopActiveKeybindRecording: StopKeybindRecording | null = null + +export function activateKeybindRecording(stop: StopKeybindRecording) { + if (stopActiveKeybindRecording === stop) return + const stopPrevious = stopActiveKeybindRecording + stopActiveKeybindRecording = stop + stopPrevious?.() +} + +export function deactivateKeybindRecording(stop: StopKeybindRecording) { + if (stopActiveKeybindRecording === stop) { + stopActiveKeybindRecording = null + } +} + +const keyMessages = defineMessages({ + unbound: { + id: 'app.settings.game-options.keybind.key.unbound', + defaultMessage: 'Unbound', + }, + escape: { + id: 'app.settings.game-options.keybind.key.escape', + defaultMessage: 'Escape', + }, + backspace: { + id: 'app.settings.game-options.keybind.key.backspace', + defaultMessage: 'Backspace', + }, + tab: { + id: 'app.settings.game-options.keybind.key.tab', + defaultMessage: 'Tab', + }, + enter: { + id: 'app.settings.game-options.keybind.key.enter', + defaultMessage: 'Enter', + }, + leftControl: { + id: 'app.settings.game-options.keybind.key.left-control', + defaultMessage: 'Left Ctrl', + }, + rightControl: { + id: 'app.settings.game-options.keybind.key.right-control', + defaultMessage: 'Right Ctrl', + }, + leftShift: { + id: 'app.settings.game-options.keybind.key.left-shift', + defaultMessage: 'Left Shift', + }, + rightShift: { + id: 'app.settings.game-options.keybind.key.right-shift', + defaultMessage: 'Right Shift', + }, + leftAlt: { + id: 'app.settings.game-options.keybind.key.left-alt', + defaultMessage: 'Left Alt', + }, + rightAlt: { + id: 'app.settings.game-options.keybind.key.right-alt', + defaultMessage: 'Right Alt', + }, + space: { + id: 'app.settings.game-options.keybind.key.space', + defaultMessage: 'Space', + }, + capsLock: { + id: 'app.settings.game-options.keybind.key.caps-lock', + defaultMessage: 'Caps Lock', + }, + numLock: { + id: 'app.settings.game-options.keybind.key.num-lock', + defaultMessage: 'Num Lock', + }, + scrollLock: { + id: 'app.settings.game-options.keybind.key.scroll-lock', + defaultMessage: 'Scroll Lock', + }, + printScreen: { + id: 'app.settings.game-options.keybind.key.print-screen', + defaultMessage: 'Print Screen', + }, + pause: { + id: 'app.settings.game-options.keybind.key.pause', + defaultMessage: 'Pause', + }, + home: { + id: 'app.settings.game-options.keybind.key.home', + defaultMessage: 'Home', + }, + end: { + id: 'app.settings.game-options.keybind.key.end', + defaultMessage: 'End', + }, + pageUp: { + id: 'app.settings.game-options.keybind.key.page-up', + defaultMessage: 'Page Up', + }, + pageDown: { + id: 'app.settings.game-options.keybind.key.page-down', + defaultMessage: 'Page Down', + }, + insert: { + id: 'app.settings.game-options.keybind.key.insert', + defaultMessage: 'Insert', + }, + delete: { + id: 'app.settings.game-options.keybind.key.delete', + defaultMessage: 'Delete', + }, + arrowUp: { + id: 'app.settings.game-options.keybind.key.arrow-up', + defaultMessage: 'Up Arrow', + }, + arrowDown: { + id: 'app.settings.game-options.keybind.key.arrow-down', + defaultMessage: 'Down Arrow', + }, + arrowLeft: { + id: 'app.settings.game-options.keybind.key.arrow-left', + defaultMessage: 'Left Arrow', + }, + arrowRight: { + id: 'app.settings.game-options.keybind.key.arrow-right', + defaultMessage: 'Right Arrow', + }, + leftSuper: { + id: 'app.settings.game-options.keybind.key.left-super', + defaultMessage: 'Left Super', + }, + rightSuper: { + id: 'app.settings.game-options.keybind.key.right-super', + defaultMessage: 'Right Super', + }, + leftCommand: { + id: 'app.settings.game-options.keybind.key.left-command', + defaultMessage: 'Left Command', + }, + rightCommand: { + id: 'app.settings.game-options.keybind.key.right-command', + defaultMessage: 'Right Command', + }, + menu: { + id: 'app.settings.game-options.keybind.key.menu', + defaultMessage: 'Menu', + }, + keypadKey: { + id: 'app.settings.game-options.keybind.key.keypad-key', + defaultMessage: 'Numpad {key}', + }, + leftMouse: { + id: 'app.settings.game-options.keybind.mouse.left', + defaultMessage: 'Left Mouse', + }, + rightMouse: { + id: 'app.settings.game-options.keybind.mouse.right', + defaultMessage: 'Right Mouse', + }, + middleMouse: { + id: 'app.settings.game-options.keybind.mouse.middle', + defaultMessage: 'Middle Mouse', + }, + mouseButton: { + id: 'app.settings.game-options.keybind.mouse.button', + defaultMessage: 'Mouse {button}', + }, + scancode: { + id: 'app.settings.game-options.keybind.scancode', + defaultMessage: 'Scancode {code}', + }, + unknownKey: { + id: 'app.settings.game-options.keybind.key.unknown', + defaultMessage: 'Unknown key', + }, + controlModifier: { + id: 'app.settings.game-options.keybind.modifier.control', + defaultMessage: 'Ctrl', + }, + shiftModifier: { + id: 'app.settings.game-options.keybind.modifier.shift', + defaultMessage: 'Shift', + }, + altModifier: { + id: 'app.settings.game-options.keybind.modifier.alt', + defaultMessage: 'Alt', + }, + commandModifier: { + id: 'app.settings.game-options.keybind.modifier.command', + defaultMessage: 'Command', + }, + superModifier: { + id: 'app.settings.game-options.keybind.modifier.super', + defaultMessage: 'Super', + }, +}) + +const keyboardCodeTokens: Record = { + Escape: 'escape', + Minus: 'minus', + Equal: 'equal', + Backspace: 'backspace', + Tab: 'tab', + BracketLeft: 'left.bracket', + BracketRight: 'right.bracket', + Enter: 'enter', + ControlLeft: 'left.control', + Semicolon: 'semicolon', + Quote: 'apostrophe', + Backquote: 'grave.accent', + ShiftLeft: 'left.shift', + Backslash: 'backslash', + Comma: 'comma', + Period: 'period', + Slash: 'slash', + ShiftRight: 'right.shift', + AltLeft: 'left.alt', + Space: 'space', + CapsLock: 'caps.lock', + NumLock: 'num.lock', + ScrollLock: 'scroll.lock', + NumpadSubtract: 'keypad.subtract', + NumpadAdd: 'keypad.add', + NumpadDecimal: 'keypad.decimal', + NumpadComma: 'keypad.decimal', + NumpadEqual: 'keypad.equal', + NumpadEnter: 'keypad.enter', + ControlRight: 'right.control', + NumpadDivide: 'keypad.divide', + NumpadMultiply: 'keypad.multiply', + PrintScreen: 'print.screen', + AltRight: 'right.alt', + Pause: 'pause', + Home: 'home', + ArrowUp: 'up', + PageUp: 'page.up', + ArrowLeft: 'left', + ArrowRight: 'right', + End: 'end', + ArrowDown: 'down', + PageDown: 'page.down', + Insert: 'insert', + Delete: 'delete', + MetaLeft: 'left.win', + MetaRight: 'right.win', + ContextMenu: 'menu', +} + +const keyboardLabels: Record = { + escape: keyMessages.escape, + minus: '-', + equal: '=', + backspace: keyMessages.backspace, + tab: keyMessages.tab, + 'left.bracket': '[', + 'right.bracket': ']', + enter: keyMessages.enter, + 'left.control': keyMessages.leftControl, + semicolon: ';', + apostrophe: "'", + 'grave.accent': '`', + 'left.shift': keyMessages.leftShift, + backslash: '\\', + comma: ',', + period: '.', + slash: '/', + 'right.shift': keyMessages.rightShift, + 'left.alt': keyMessages.leftAlt, + space: keyMessages.space, + 'caps.lock': keyMessages.capsLock, + 'num.lock': keyMessages.numLock, + 'scroll.lock': keyMessages.scrollLock, + 'keypad.subtract': '-', + 'keypad.add': '+', + 'keypad.decimal': '.', + 'keypad.equal': '=', + 'keypad.enter': keyMessages.enter, + 'right.control': keyMessages.rightControl, + 'keypad.divide': '/', + 'keypad.multiply': '*', + 'print.screen': keyMessages.printScreen, + 'right.alt': keyMessages.rightAlt, + pause: keyMessages.pause, + home: keyMessages.home, + up: keyMessages.arrowUp, + 'page.up': keyMessages.pageUp, + left: keyMessages.arrowLeft, + right: keyMessages.arrowRight, + end: keyMessages.end, + down: keyMessages.arrowDown, + 'page.down': keyMessages.pageDown, + insert: keyMessages.insert, + delete: keyMessages.delete, + menu: keyMessages.menu, +} + +function splitKeybind(value: string): [key: string, modifier?: string] { + const separator = value.indexOf(':') + return separator === -1 ? [value] : [value.slice(0, separator), value.slice(separator + 1)] +} + +function keyboardIdentifierFromCode(code: string): string | null { + const letter = /^Key([A-Z])$/.exec(code) + if (letter) return letter[1].toLowerCase() + + const digit = /^Digit([0-9])$/.exec(code) + if (digit) return digit[1] + + const functionKey = /^F([1-9]|1[0-9]|2[0-5])$/.exec(code) + if (functionKey) return `f${functionKey[1]}` + + const keypadDigit = /^Numpad([0-9])$/.exec(code) + if (keypadDigit) return `keypad.${keypadDigit[1]}` + + return keyboardCodeTokens[code] ?? null +} + +export function minecraftKeyTokenFromKeyboardEvent(event: KeyboardEvent): string | null { + const identifier = keyboardIdentifierFromCode(event.code) + return identifier ? `key.keyboard.${identifier}` : null +} + +export function minecraftMouseTokenFromButton(button: number): string | null { + if (!Number.isInteger(button) || button < 0 || button > 15) return null + if (button === 0) return 'key.mouse.left' + if (button === 1) return 'key.mouse.middle' + if (button === 2) return 'key.mouse.right' + return `key.mouse.${button + 1}` +} + +function formatKeyboardLabel( + formatMessage: FormatMessage, + identifier: string, + isMac: boolean, +): string { + if (identifier === 'unknown') return formatMessage(keyMessages.unbound) + if (/^[a-z]$/.test(identifier)) return identifier.toUpperCase() + if (/^[0-9]$/.test(identifier)) return identifier + if (/^f([1-9]|1[0-9]|2[0-5])$/.test(identifier)) return identifier.toUpperCase() + + const keypad = /^keypad\.(.+)$/.exec(identifier) + if (keypad) { + const label = keyboardLabels[identifier] + const key = typeof label === 'string' ? label : label ? formatMessage(label) : keypad[1] + return formatMessage(keyMessages.keypadKey, { key }) + } + + if (identifier === 'left.win') { + return formatMessage(isMac ? keyMessages.leftCommand : keyMessages.leftSuper) + } + if (identifier === 'right.win') { + return formatMessage(isMac ? keyMessages.rightCommand : keyMessages.rightSuper) + } + + const label = keyboardLabels[identifier] + if (typeof label === 'string') return label + if (label) return formatMessage(label) + return identifier + .split('.') + .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`) + .join(' ') +} + +function formatModifier(formatMessage: FormatMessage, modifier: string, isMac: boolean): string { + switch (modifier.toUpperCase()) { + case 'CONTROL': + case 'CTRL': + return formatMessage(keyMessages.controlModifier) + case 'SHIFT': + return formatMessage(keyMessages.shiftModifier) + case 'ALT': + return formatMessage(keyMessages.altModifier) + case 'META': + case 'SUPER': + return formatMessage(isMac ? keyMessages.commandModifier : keyMessages.superModifier) + default: + return modifier + } +} + +export function formatMinecraftKeybind( + formatMessage: FormatMessage, + value: string, + isMac: boolean, +): string { + const [key, modifier] = splitKeybind(value) + let label: string + + if (key.startsWith('key.keyboard.')) { + label = formatKeyboardLabel(formatMessage, key.slice('key.keyboard.'.length), isMac) + } else if (key === 'key.mouse.left') { + label = formatMessage(keyMessages.leftMouse) + } else if (key === 'key.mouse.right') { + label = formatMessage(keyMessages.rightMouse) + } else if (key === 'key.mouse.middle') { + label = formatMessage(keyMessages.middleMouse) + } else if (key.startsWith('key.mouse.')) { + label = formatMessage(keyMessages.mouseButton, { + button: key.slice('key.mouse.'.length), + }) + } else if (key.startsWith('scancode.')) { + label = formatMessage(keyMessages.scancode, { code: key.slice('scancode.'.length) }) + } else { + label = formatMessage(keyMessages.unknownKey) + } + + if (!modifier || key === 'key.keyboard.unknown') return label + const modifiers = modifier + .split(/[+:]/) + .filter(Boolean) + .map((part) => formatModifier(formatMessage, part, isMac)) + return [...modifiers, label].join(' + ') +} + +export function minecraftKeybindConflictKey(optionId: string, value: string): string | null { + const normalized = value.trim().toLowerCase() + if (!normalized || normalized === 'key.keyboard.unknown') return null + + if (optionId === 'key.debug.modifier') return null + if (optionId.startsWith('key.debug.') && optionId !== 'key.debug.overlay') { + return `debug:${normalized}` + } + return `direct:${normalized}` +} diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/languages.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/languages.ts new file mode 100644 index 0000000..752e3aa --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/languages.ts @@ -0,0 +1,152 @@ +import type { ComboboxOption } from '@modrinth/ui' + +/** Java Edition language names and regions from https://github.com/misode/mcmeta/blob/assets/pack.mcmeta. */ +const languageNames: Record = { + en_us: 'English (US)', + af_za: 'Afrikaans (Suid-Afrika)', + esan: 'Andalûh (Andaluçía)', + enp: 'Anglish (Oned Riches)', + ast_es: 'Asturianu (Asturies)', + az_az: 'Azərbaycanca (Azərbaycan)', + id_id: 'Bahasa Indonesia (Indonesia)', + ms_my: 'Bahasa Melayu (Malaysia)', + tzo_mx: "Bats'i k'op (Jobel)", + qid: 'Bhs. Indonesia edjaän lama (Indonesia tempo doeloe)', + be_latn: 'Biełaruskaja (Biełaruś)', + bar: 'Boarisch (Bayern)', + bs_ba: 'Bosanski (Bosna i Hercegovina)', + brb: 'Braobans (Braobant)', + br_fr: 'Brezhoneg (Breizh)', + ca_es: 'Català (Catalunya)', + val_es: 'Català (Valencià) (País Valencià)', + cy_gb: 'Cymraeg (Cymru)', + qcb_es: 'Cántabru/Montañés (Cantabria)', + da_dk: 'Dansk (Danmark)', + se_no: 'Davvisámegiella (Sápmi)', + de_at: 'Deitsch (Österreich)', + de_de: 'Deutsch (Deutschland)', + et_ee: 'Eesti keel (Eesti)', + en_au: 'English (Australia)', + en_ca: 'English (Canada)', + en_nz: 'English (New Zealand)', + en_gb: 'English (United Kingdom)', + es_ar: 'Español (Argentina)', + es_cl: 'Español (Chile)', + es_ec: 'Español (Ecuador)', + es_es: 'Español (España)', + es_mx: 'Español (México)', + es_uy: 'Español (Uruguay)', + es_ve: 'Español (Venezuela)', + eo_uy: 'Esperanto (Esperantujo)', + eu_es: 'Euskara (Euskal Herria)', + fil_ph: 'Filipino (Pilipinas)', + fr_ca: 'Français (Canada)', + fr_fr: 'Français (France)', + fr_ch: 'Français (Suisse)', + fy_nl: 'Frysk (Fryslân)', + fra_de: 'Fränggisch (Franggn)', + fur_it: 'Furlan (Friûl)', + fo_fo: 'Føroyskt (Føroyar)', + ga_ie: 'Gaeilge (Éire)', + gl_es: 'Galego (Galicia / Galiza)', + go_fr: 'Galo (Bertègn)', + gd_gb: 'Gàidhlig (Alba)', + hr_hr: 'Hrvatski (Hrvatska)', + hn_no: 'Høgnorsk (Norig)', + io_en: 'Ido (Idia)', + ig_ng: 'Igbo (Naigeria)', + it_it: 'Italiano (Italia)', + kw_gb: 'Kernewek (Kernow)', + ksh: 'Kölsch/Ripoarisch (Rhingland)', + lol_us: 'LOLCAT (Kingdom of Cats)', + la_la: 'Latina (Latium)', + lv_lv: 'Latviešu (Latvija)', + lt_lt: 'Lietuvių (Lietuva)', + li_li: 'Limburgs (Limburg)', + lmo: 'Lombard (Lombardia)', + lb_lu: 'Lëtzebuergesch (Lëtzebuerg)', + hu_hu: 'Magyar (Magyarország)', + mt_mt: 'Malti (Malta)', + isv: 'Medžuslovjansky (Slovjanščina)', + nah: 'Mēxikatlahtōlli (Mēxiko)', + nl_nl: 'Nederlands (Nederland)', + pls: 'Ngiiwà (Ndanìꞌngà)', + no_no: 'Norsk bokmål (Norge)', + nn_no: 'Norsk nynorsk (Noreg)', + uz_uz: "O'zbekcha (O'zbekiston)", + oc_fr: 'Occitan (Occitània)', + en_pt: 'Pirate Speak (The Seven Seas)', + nds_de: 'Plattdüütsch (Düütschland)', + pl_pl: 'Polski (Polska)', + pt_br: 'Português (Brasil)', + pt_pt: 'Português (Portugal)', + qya_aa: 'Quenya (Arda)', + ro_ro: 'Română (România)', + de_ch: 'Schwiizerdütsch (Schwiiz)', + enws: 'Shakespearean English (Kingdom of England)', + sq_al: 'Shqip (Shqipëri)', + sk_sk: 'Slovenčina (Slovensko)', + sl_si: 'Slovenščina (Slovenija)', + so_so: 'Soomaali (Soomaaliya)', + sr_cs: 'Srpski (Srbija)', + fi_fi: 'Suomi (Suomi)', + sv_se: 'Svenska (Sverige)', + sxu: 'Säggs’sch (Saggsn)', + tl_ph: 'Tagalog (Pilipinas)', + vi_vn: 'Tiếng Việt (Việt Nam)', + tr_tr: 'Türkçe (Türkiye)', + vp_vl: 'Viossa (Vilant)', + nl_be: 'Vlaams (België)', + vec_it: 'Vèneto (Veneto)', + vro: 'Võro (Eesti)', + yo_ng: 'Yorùbá (Nàìjíríà)', + jbo_en: "la .lojban. (la jbogu'e)", + tlh_aa: "tlhIngan Hol (tlhIngan wo')", + tok: 'toki pona (kulupu pona)', + is_is: 'Íslenska (Ísland)', + ovd: 'Övdalska (Swerre)', + cs_cz: 'Čeština (Česko)', + szl: 'Ślōnski (Gōrny Ślōnsk)', + en_ud: 'ɥsᴉꞁᵷuƎ (uʍoᗡ ǝpᴉsd∩)', + haw_us: 'ʻŌlelo Hawaiʻi (Hawaiʻi)', + el_gr: 'Ελληνικά (Ελλάδα)', + ba_ru: 'Башҡортса (Башҡортостан, Рәсәй)', + be_by: 'Беларуская (Беларусь)', + bg_bg: 'Български (България)', + hal_ua: 'Галицка (Галичина, Вкраїна)', + ky_kg: 'Кыргызча (Кыргызстан)', + mk_mk: 'Македонски (Северна Македонија)', + mn_mn: 'Монгол (Монгол Улс)', + ry_ua: 'Руснацькый (Пудкарпатя, Украина)', + ru_ru: 'Русский (Россия)', + rpr: 'Русскій дореформенный (Россійская имперія)', + sah_sah: 'Сахалыы (Cаха Сирэ)', + sr_sp: 'Српски (Србија)', + tt_ru: 'Татарча (Татарстан, Рәсәй)', + uk_ua: 'Українська (Україна)', + cv_cu: 'Чӑвашла (Чӑваш Ен, Раҫҫей)', + kk_kz: 'Қазақша (Қазақстан)', + hy_am: 'Հայերեն (Հայաստան)', + yi_de: 'ייִדיש (אשכנזיש יידן)', + he_il: 'עברית (ישראל)', + ar_sa: 'العربية (العالم العربي)', + zlm_arab: 'بهاس ملايو (مليسيا)', + fa_ir: 'فارسی (ايران)', + hi_in: 'हिंदी (भारत)', + ta_in: 'தமிழ் (இந்தியா)', + kn_in: 'ಕನ್ನಡ (ಭಾರತ)', + th_th: 'ไทย (ประเทศไทย)', + lo_la: 'ລາວ (ປະເທດລາວ)', + ka_ge: 'ქართული (საქართველო)', + lzh: '文言 (華夏)', + ja_jp: '日本語 (日本)', + zh_cn: '简体中文 (中国大陆)', + zh_tw: '繁體中文 (台灣)', + zh_hk: '繁體中文 (香港特別行政區)', + ko_kr: '한국어 (대한민국)', + got_de: '𐌲𐌿𐍄𐍂𐌰𐌶𐌳𐌰 (𐌲𐌿𐍄𐌸𐌹𐌿𐌳𐌰)', +} + +export const minecraftLanguageOptions: ComboboxOption[] = Object.entries(languageNames) + .map(([value, label]) => ({ value, label, searchTerms: [value] })) + .sort((a, b) => a.label.localeCompare(b.label)) diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/messages.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/messages.ts new file mode 100644 index 0000000..a5e741b --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/messages.ts @@ -0,0 +1,1428 @@ +import { defineMessages, type MessageDescriptor, type VIntlFormatters } from '@modrinth/ui' + +import type { + EditableGameSetting, + GameOptionValidationError, + GameSettingCategory, +} from '@/helpers/game-options' + +type FormatMessage = VIntlFormatters['formatMessage'] + +const settingMessages = defineMessages({ + fovLabel: { id: 'app.settings.game-options.setting.fov.label', defaultMessage: 'Field of view' }, + graphicsLabel: { + id: 'app.settings.game-options.setting.graphics.label', + defaultMessage: 'Graphics', + }, + graphicsDescription: { + id: 'app.settings.game-options.setting.graphics.description', + defaultMessage: 'Controls visual quality and performance.', + }, + ambientOcclusionLabel: { + id: 'app.settings.game-options.setting.ambient-occlusion.label', + defaultMessage: 'Smooth lighting', + }, + renderDistanceLabel: { + id: 'app.settings.game-options.setting.render-distance.label', + defaultMessage: 'Render distance', + }, + simulationDistanceLabel: { + id: 'app.settings.game-options.setting.simulation-distance.label', + defaultMessage: 'Simulation distance', + }, + simulationDistanceDescription: { + id: 'app.settings.game-options.setting.simulation-distance.description', + defaultMessage: 'How far away entities update and blocks and fluids tick.', + }, + guiScaleLabel: { + id: 'app.settings.game-options.setting.gui-scale.label', + defaultMessage: 'GUI scale', + }, + guiScaleDescription: { + id: 'app.settings.game-options.setting.gui-scale.description', + defaultMessage: 'The size of the game interface and HUD.', + }, + particlesLabel: { + id: 'app.settings.game-options.setting.particles.label', + defaultMessage: 'Particles', + }, + cloudsLabel: { id: 'app.settings.game-options.setting.clouds.label', defaultMessage: 'Clouds' }, + entityShadowsLabel: { + id: 'app.settings.game-options.setting.entity-shadows.label', + defaultMessage: 'Entity shadows', + }, + viewBobbingLabel: { + id: 'app.settings.game-options.setting.view-bobbing.label', + defaultMessage: 'View bobbing', + }, + viewBobbingDescription: { + id: 'app.settings.game-options.setting.view-bobbing.description', + defaultMessage: 'Add a bobbing motion to the camera while walking.', + }, + vsyncLabel: { id: 'app.settings.game-options.setting.vsync.label', defaultMessage: 'VSync' }, + vsyncDescription: { + id: 'app.settings.game-options.setting.vsync.description', + defaultMessage: 'Limit the frame rate to the display refresh rate to prevent screen tearing.', + }, + fullscreenLabel: { + id: 'app.settings.game-options.setting.fullscreen.label', + defaultMessage: 'Fullscreen', + }, + maxFramerateLabel: { + id: 'app.settings.game-options.setting.max-framerate.label', + defaultMessage: 'Maximum framerate', + }, + mipmapLevelsLabel: { + id: 'app.settings.game-options.setting.mipmap-levels.label', + defaultMessage: 'Mipmap levels', + }, + mipmapLevelsDescription: { + id: 'app.settings.game-options.setting.mipmap-levels.description', + defaultMessage: 'Texture smoothing at a distance.', + }, + biomeBlendRadiusLabel: { + id: 'app.settings.game-options.setting.biome-blend-radius.label', + defaultMessage: 'Biome blend', + }, + biomeBlendRadiusDescription: { + id: 'app.settings.game-options.setting.biome-blend-radius.description', + defaultMessage: 'The distance over which biome colors transition.', + }, + languageLabel: { + id: 'app.settings.game-options.setting.language.label', + defaultMessage: 'Language', + }, + masterVolumeLabel: { + id: 'app.settings.game-options.setting.master-volume.label', + defaultMessage: 'Master volume', + }, + musicVolumeLabel: { + id: 'app.settings.game-options.setting.music-volume.label', + defaultMessage: 'Music', + }, + musicToastLabel: { + id: 'app.settings.game-options.setting.music-toast.label', + defaultMessage: 'Music notification', + }, + musicToastDescription: { + id: 'app.settings.game-options.setting.music-toast.description', + defaultMessage: 'Choose whether music titles appear in the pause menu and as toasts.', + }, + recordVolumeLabel: { + id: 'app.settings.game-options.setting.record-volume.label', + defaultMessage: 'Jukebox/Note Blocks', + }, + weatherVolumeLabel: { + id: 'app.settings.game-options.setting.weather-volume.label', + defaultMessage: 'Weather', + }, + blocksVolumeLabel: { + id: 'app.settings.game-options.setting.blocks-volume.label', + defaultMessage: 'Blocks', + }, + hostileVolumeLabel: { + id: 'app.settings.game-options.setting.hostile-volume.label', + defaultMessage: 'Hostile creatures', + }, + neutralVolumeLabel: { + id: 'app.settings.game-options.setting.neutral-volume.label', + defaultMessage: 'Friendly creatures', + }, + playersVolumeLabel: { + id: 'app.settings.game-options.setting.players-volume.label', + defaultMessage: 'Players', + }, + ambientVolumeLabel: { + id: 'app.settings.game-options.setting.ambient-volume.label', + defaultMessage: 'Ambient/Environment', + }, + voiceVolumeLabel: { + id: 'app.settings.game-options.setting.voice-volume.label', + defaultMessage: 'Voice and speech', + }, + uiVolumeLabel: { + id: 'app.settings.game-options.setting.ui-volume.label', + defaultMessage: 'UI', + }, + sensitivityLabel: { + id: 'app.settings.game-options.setting.sensitivity.label', + defaultMessage: 'Mouse sensitivity', + }, + invertMouseLabel: { + id: 'app.settings.game-options.setting.invert-mouse.label', + defaultMessage: 'Invert mouse', + }, + invertMouseDescription: { + id: 'app.settings.game-options.setting.invert-mouse.description', + defaultMessage: 'Invert vertical mouse movement.', + }, + autoJumpLabel: { + id: 'app.settings.game-options.setting.auto-jump.label', + defaultMessage: 'Auto-jump', + }, + autoJumpDescription: { + id: 'app.settings.game-options.setting.auto-jump.description', + defaultMessage: 'Automatically jump up one-block-high obstacles.', + }, + toggleCrouchLabel: { + id: 'app.settings.game-options.setting.toggle-crouch.label', + defaultMessage: 'Toggle crouch', + }, + toggleCrouchDescription: { + id: 'app.settings.game-options.setting.toggle-crouch.description', + defaultMessage: 'Press once to remain crouched.', + }, + toggleSprintLabel: { + id: 'app.settings.game-options.setting.toggle-sprint.label', + defaultMessage: 'Toggle sprint', + }, + toggleSprintDescription: { + id: 'app.settings.game-options.setting.toggle-sprint.description', + defaultMessage: 'Press once to remain sprinting.', + }, + discreteMouseScrollLabel: { + id: 'app.settings.game-options.setting.discrete-mouse-scroll.label', + defaultMessage: 'Discrete scrolling', + }, + discreteMouseScrollDescription: { + id: 'app.settings.game-options.setting.discrete-mouse-scroll.description', + defaultMessage: 'Treat each mouse-wheel input as a single scroll step.', + }, + keyForwardLabel: { + id: 'app.settings.game-options.setting.key-forward.label', + defaultMessage: 'Move forward', + }, + keyLeftLabel: { + id: 'app.settings.game-options.setting.key-left.label', + defaultMessage: 'Strafe left', + }, + keyBackLabel: { + id: 'app.settings.game-options.setting.key-back.label', + defaultMessage: 'Move backward', + }, + keyRightLabel: { + id: 'app.settings.game-options.setting.key-right.label', + defaultMessage: 'Strafe right', + }, + keyJumpLabel: { id: 'app.settings.game-options.setting.key-jump.label', defaultMessage: 'Jump' }, + keySneakLabel: { + id: 'app.settings.game-options.setting.key-sneak.label', + defaultMessage: 'Sneak', + }, + keySprintLabel: { + id: 'app.settings.game-options.setting.key-sprint.label', + defaultMessage: 'Sprint', + }, + keyInventoryLabel: { + id: 'app.settings.game-options.setting.key-inventory.label', + defaultMessage: 'Inventory', + }, + keySwapOffhandLabel: { + id: 'app.settings.game-options.setting.key-swap-offhand.label', + defaultMessage: 'Swap offhand', + }, + keyDropLabel: { + id: 'app.settings.game-options.setting.key-drop.label', + defaultMessage: 'Drop item', + }, + keyUseLabel: { + id: 'app.settings.game-options.setting.key-use.label', + defaultMessage: 'Use item', + }, + keyAttackLabel: { + id: 'app.settings.game-options.setting.key-attack.label', + defaultMessage: 'Attack', + }, + keyPickItemLabel: { + id: 'app.settings.game-options.setting.key-pick-item.label', + defaultMessage: 'Pick block', + }, + keyChatLabel: { + id: 'app.settings.game-options.setting.key-chat.label', + defaultMessage: 'Open chat', + }, + keyPlayerListLabel: { + id: 'app.settings.game-options.setting.key-player-list.label', + defaultMessage: 'Player list', + }, + keyCommandLabel: { + id: 'app.settings.game-options.setting.key-command.label', + defaultMessage: 'Command', + }, + keyScreenshotLabel: { + id: 'app.settings.game-options.setting.key-screenshot.label', + defaultMessage: 'Screenshot', + }, + keyPerspectiveLabel: { + id: 'app.settings.game-options.setting.key-perspective.label', + defaultMessage: 'Change perspective', + }, + keyFullscreenLabel: { + id: 'app.settings.game-options.setting.key-fullscreen.label', + defaultMessage: 'Toggle fullscreen', + }, + keyAdvancementsLabel: { + id: 'app.settings.game-options.setting.key-advancements.label', + defaultMessage: 'Advancements', + }, + chatVisibilityLabel: { + id: 'app.settings.game-options.setting.chat-visibility.label', + defaultMessage: 'Chat visibility', + }, + chatColorsLabel: { + id: 'app.settings.game-options.setting.chat-colors.label', + defaultMessage: 'Chat colors', + }, + chatLinksLabel: { + id: 'app.settings.game-options.setting.chat-links.label', + defaultMessage: 'Web links', + }, + chatLinksDescription: { + id: 'app.settings.game-options.setting.chat-links.description', + defaultMessage: 'Allow web links in chat to be opened.', + }, + chatLinksPromptLabel: { + id: 'app.settings.game-options.setting.chat-links-prompt.label', + defaultMessage: 'Prompt on links', + }, + chatLinksPromptDescription: { + id: 'app.settings.game-options.setting.chat-links-prompt.description', + defaultMessage: 'Ask before opening links from chat.', + }, + chatOpacityLabel: { + id: 'app.settings.game-options.setting.chat-opacity.label', + defaultMessage: 'Chat opacity', + }, + chatOpacityDescription: { + id: 'app.settings.game-options.setting.chat-opacity.description', + defaultMessage: 'The opacity of chat text.', + }, + chatScaleLabel: { + id: 'app.settings.game-options.setting.chat-scale.label', + defaultMessage: 'Chat scale', + }, + narratorLabel: { + id: 'app.settings.game-options.setting.narrator.label', + defaultMessage: 'Narrator', + }, + narratorDescription: { + id: 'app.settings.game-options.setting.narrator.description', + defaultMessage: 'Choose what the narrator reads.', + }, + subtitlesLabel: { + id: 'app.settings.game-options.setting.subtitles.label', + defaultMessage: 'Subtitles', + }, + subtitlesDescription: { + id: 'app.settings.game-options.setting.subtitles.description', + defaultMessage: 'Show captions for sounds played in the game.', + }, + highContrastLabel: { + id: 'app.settings.game-options.setting.high-contrast.label', + defaultMessage: 'High contrast', + }, + highContrastDescription: { + id: 'app.settings.game-options.setting.high-contrast.description', + defaultMessage: 'Enhance the contrast of interface elements.', + }, + darkSplashLabel: { + id: 'app.settings.game-options.setting.dark-splash.label', + defaultMessage: 'Monochrome logo', + }, + darkSplashDescription: { + id: 'app.settings.game-options.setting.dark-splash.description', + defaultMessage: 'Change the Mojang Studios loading screen from red to black.', + }, + notificationTimeLabel: { + id: 'app.settings.game-options.setting.notification-time.label', + defaultMessage: 'Notification time', + }, + notificationTimeDescription: { + id: 'app.settings.game-options.setting.notification-time.description', + defaultMessage: 'How long toast notifications remain visible.', + }, + mainHandLabel: { + id: 'app.settings.game-options.setting.main-hand.label', + defaultMessage: 'Main hand', + }, + mainHandDescription: { + id: 'app.settings.game-options.setting.main-hand.description', + defaultMessage: 'Choose whether the main hand is left or right.', + }, + capeLabel: { id: 'app.settings.game-options.setting.cape.label', defaultMessage: 'Cape' }, + capeDescription: { + id: 'app.settings.game-options.setting.cape.description', + defaultMessage: "Show the player's cape, including its elytra texture.", + }, + hatLabel: { id: 'app.settings.game-options.setting.hat.label', defaultMessage: 'Hat' }, + hatDescription: { + id: 'app.settings.game-options.setting.hat.description', + defaultMessage: 'Show the hat skin layer.', + }, + jacketLabel: { id: 'app.settings.game-options.setting.jacket.label', defaultMessage: 'Jacket' }, + jacketDescription: { + id: 'app.settings.game-options.setting.jacket.description', + defaultMessage: 'Show the jacket skin layer.', + }, + allowServerListingLabel: { + id: 'app.settings.game-options.setting.allow-server-listing.label', + defaultMessage: 'Server listings', + }, + allowServerListingDescription: { + id: 'app.settings.game-options.setting.allow-server-listing.description', + defaultMessage: "Allow the player's name to appear in server listings.", + }, + realmsNotificationsLabel: { + id: 'app.settings.game-options.setting.realms-notifications.label', + defaultMessage: 'Realms notifications', + }, +}) + +const catalogSettingMessages = defineMessages({ + brightnessLabel: { + id: 'app.settings.game-options.setting.brightness.label', + defaultMessage: 'Brightness', + }, + legacyViewDistanceLabel: { + id: 'app.settings.game-options.setting.legacy-view-distance.label', + defaultMessage: 'View distance', + }, + entityDistanceLabel: { + id: 'app.settings.game-options.setting.entity-distance.label', + defaultMessage: 'Entity distance', + }, + debugGuiScaleLabel: { + id: 'app.settings.game-options.setting.debug-gui-scale.label', + defaultMessage: 'Debug GUI scale', + }, + graphicsBackendLabel: { + id: 'app.settings.game-options.setting.graphics-backend.label', + defaultMessage: 'Graphics backend', + }, + cloudRangeLabel: { + id: 'app.settings.game-options.setting.cloud-range.label', + defaultMessage: 'Cloud distance', + }, + exclusiveFullscreenLabel: { + id: 'app.settings.game-options.setting.exclusive-fullscreen.label', + defaultMessage: 'Exclusive fullscreen', + }, + macFullscreenMenuLabel: { + id: 'app.settings.game-options.setting.mac-fullscreen-menu.label', + defaultMessage: 'Show macOS menu in fullscreen', + }, + legacyFramerateLimitLabel: { + id: 'app.settings.game-options.setting.legacy-framerate-limit.label', + defaultMessage: 'Framerate limit', + }, + inactivityFramerateLimitLabel: { + id: 'app.settings.game-options.setting.inactivity-framerate-limit.label', + defaultMessage: 'Reduced framerate', + }, + prioritizeChunkUpdatesLabel: { + id: 'app.settings.game-options.setting.prioritize-chunk-updates.label', + defaultMessage: 'Prioritize chunk updates', + }, + attackIndicatorLabel: { + id: 'app.settings.game-options.setting.attack-indicator.label', + defaultMessage: 'Attack indicator', + }, + reducedDebugInfoLabel: { + id: 'app.settings.game-options.setting.reduced-debug-info.label', + defaultMessage: 'Reduced debug information', + }, + chunkFadeTimeLabel: { + id: 'app.settings.game-options.setting.chunk-fade-time.label', + defaultMessage: 'Chunk fade time', + }, + cutoutLeavesLabel: { + id: 'app.settings.game-options.setting.cutout-leaves.label', + defaultMessage: 'Cutout leaves', + }, + improvedTransparencyLabel: { + id: 'app.settings.game-options.setting.improved-transparency.label', + defaultMessage: 'Improved transparency', + }, + textureFilteringLabel: { + id: 'app.settings.game-options.setting.texture-filtering.label', + defaultMessage: 'Texture filtering', + }, + anisotropyLabel: { + id: 'app.settings.game-options.setting.anisotropy.label', + defaultMessage: 'Anisotropy', + }, + vignetteLabel: { + id: 'app.settings.game-options.setting.vignette.label', + defaultMessage: 'Vignette', + }, + weatherRadiusLabel: { + id: 'app.settings.game-options.setting.weather-radius.label', + defaultMessage: 'Weather radius', + }, + advancedOpenGlLabel: { + id: 'app.settings.game-options.setting.advanced-opengl.label', + defaultMessage: 'Advanced OpenGL', + }, + anaglyph3dLabel: { + id: 'app.settings.game-options.setting.anaglyph-3d.label', + defaultMessage: '3D anaglyph', + }, + anisotropicFilteringLabel: { + id: 'app.settings.game-options.setting.anisotropic-filtering.label', + defaultMessage: 'Anisotropic filtering', + }, + alternateBlocksLabel: { + id: 'app.settings.game-options.setting.alternate-blocks.label', + defaultMessage: 'Alternate blocks', + }, + heldItemTooltipsLabel: { + id: 'app.settings.game-options.setting.held-item-tooltips.label', + defaultMessage: 'Held item tooltips', + }, + useVboLabel: { + id: 'app.settings.game-options.setting.use-vbo.label', + defaultMessage: 'Use VBOs', + }, + forceUnicodeFontLabel: { + id: 'app.settings.game-options.setting.force-unicode-font.label', + defaultMessage: 'Force Unicode font', + }, + japaneseGlyphVariantsLabel: { + id: 'app.settings.game-options.setting.japanese-glyph-variants.label', + defaultMessage: 'Japanese glyph variants', + }, + musicFrequencyLabel: { + id: 'app.settings.game-options.setting.music-frequency.label', + defaultMessage: 'Music frequency', + }, + directionalAudioLabel: { + id: 'app.settings.game-options.setting.directional-audio.label', + defaultMessage: 'Directional audio', + }, + invertHorizontalMouseLabel: { + id: 'app.settings.game-options.setting.invert-horizontal-mouse.label', + defaultMessage: 'Invert horizontal mouse', + }, + toggleAttackLabel: { + id: 'app.settings.game-options.setting.toggle-attack.label', + defaultMessage: 'Toggle attack', + }, + toggleUseLabel: { + id: 'app.settings.game-options.setting.toggle-use.label', + defaultMessage: 'Toggle use', + }, + mouseWheelSensitivityLabel: { + id: 'app.settings.game-options.setting.mouse-wheel-sensitivity.label', + defaultMessage: 'Mouse wheel sensitivity', + }, + rawMouseInputLabel: { + id: 'app.settings.game-options.setting.raw-mouse-input.label', + defaultMessage: 'Raw mouse input', + }, + touchscreenLabel: { + id: 'app.settings.game-options.setting.touchscreen.label', + defaultMessage: 'Touchscreen mode', + }, + allowCursorChangesLabel: { + id: 'app.settings.game-options.setting.allow-cursor-changes.label', + defaultMessage: 'Allow cursor changes', + }, + sprintWindowLabel: { + id: 'app.settings.game-options.setting.sprint-window.label', + defaultMessage: 'Sprint window', + }, + operatorItemsTabLabel: { + id: 'app.settings.game-options.setting.operator-items-tab.label', + defaultMessage: 'Operator items tab', + }, + ctrlClickRightClickLabel: { + id: 'app.settings.game-options.setting.ctrl-click-right-click.label', + defaultMessage: 'Control-click as right-click', + }, + quitShortcutsLabel: { + id: 'app.settings.game-options.setting.quit-shortcuts.label', + defaultMessage: 'Quit shortcuts', + }, + chatWidthLabel: { + id: 'app.settings.game-options.setting.chat-width.label', + defaultMessage: 'Chat width', + }, + focusedChatHeightLabel: { + id: 'app.settings.game-options.setting.focused-chat-height.label', + defaultMessage: 'Focused chat height', + }, + unfocusedChatHeightLabel: { + id: 'app.settings.game-options.setting.unfocused-chat-height.label', + defaultMessage: 'Unfocused chat height', + }, + chatLineSpacingLabel: { + id: 'app.settings.game-options.setting.chat-line-spacing.label', + defaultMessage: 'Chat line spacing', + }, + chatDelayLabel: { + id: 'app.settings.game-options.setting.chat-delay.label', + defaultMessage: 'Chat delay', + }, + textBackgroundOpacityLabel: { + id: 'app.settings.game-options.setting.text-background-opacity.label', + defaultMessage: 'Text background opacity', + }, + chatBackgroundOnlyLabel: { + id: 'app.settings.game-options.setting.chat-background-only.label', + defaultMessage: 'Chat background only', + }, + autoSuggestionsLabel: { + id: 'app.settings.game-options.setting.auto-suggestions.label', + defaultMessage: 'Command suggestions', + }, + secureChatOnlyLabel: { + id: 'app.settings.game-options.setting.secure-chat-only.label', + defaultMessage: 'Only show secure chat', + }, + saveChatDraftsLabel: { + id: 'app.settings.game-options.setting.save-chat-drafts.label', + defaultMessage: 'Save chat drafts', + }, + hideMatchedNamesLabel: { + id: 'app.settings.game-options.setting.hide-matched-names.label', + defaultMessage: 'Hide matched names', + }, + chatPreviewLabel: { + id: 'app.settings.game-options.setting.chat-preview.label', + defaultMessage: 'Chat preview', + }, + fovEffectsLabel: { + id: 'app.settings.game-options.setting.fov-effects.label', + defaultMessage: 'FOV effects', + }, + screenEffectsLabel: { + id: 'app.settings.game-options.setting.screen-effects.label', + defaultMessage: 'Screen effects', + }, + darknessPulsingLabel: { + id: 'app.settings.game-options.setting.darkness-pulsing.label', + defaultMessage: 'Darkness pulsing', + }, + damageTiltLabel: { + id: 'app.settings.game-options.setting.damage-tilt.label', + defaultMessage: 'Damage tilt', + }, + glintSpeedLabel: { + id: 'app.settings.game-options.setting.glint-speed.label', + defaultMessage: 'Glint speed', + }, + glintStrengthLabel: { + id: 'app.settings.game-options.setting.glint-strength.label', + defaultMessage: 'Glint strength', + }, + hideLightningFlashesLabel: { + id: 'app.settings.game-options.setting.hide-lightning-flashes.label', + defaultMessage: 'Hide lightning flashes', + }, + hideSplashTextsLabel: { + id: 'app.settings.game-options.setting.hide-splash-texts.label', + defaultMessage: 'Hide splash texts', + }, + highContrastOutlineLabel: { + id: 'app.settings.game-options.setting.high-contrast-outline.label', + defaultMessage: 'High contrast block outline', + }, + narratorHotkeyLabel: { + id: 'app.settings.game-options.setting.narrator-hotkey.label', + defaultMessage: 'Narrator hotkey', + }, + autosaveIndicatorLabel: { + id: 'app.settings.game-options.setting.autosave-indicator.label', + defaultMessage: 'Autosave indicator', + }, + panoramaSpeedLabel: { + id: 'app.settings.game-options.setting.panorama-speed.label', + defaultMessage: 'Panorama speed', + }, + menuBackgroundBlurLabel: { + id: 'app.settings.game-options.setting.menu-background-blur.label', + defaultMessage: 'Menu background blur', + }, + rotateWithMinecartLabel: { + id: 'app.settings.game-options.setting.rotate-with-minecart.label', + defaultMessage: 'Rotate with minecart', + }, + leftSleeveLabel: { + id: 'app.settings.game-options.setting.left-sleeve.label', + defaultMessage: 'Left sleeve', + }, + rightSleeveLabel: { + id: 'app.settings.game-options.setting.right-sleeve.label', + defaultMessage: 'Right sleeve', + }, + leftPantsLegLabel: { + id: 'app.settings.game-options.setting.left-pants-leg.label', + defaultMessage: 'Left pants leg', + }, + rightPantsLegLabel: { + id: 'app.settings.game-options.setting.right-pants-leg.label', + defaultMessage: 'Right pants leg', + }, + hideServerAddressLabel: { + id: 'app.settings.game-options.setting.hide-server-address.label', + defaultMessage: 'Hide server address', + }, + serverTexturesLabel: { + id: 'app.settings.game-options.setting.server-textures.label', + defaultMessage: 'Server textures', + }, + snooperLabel: { + id: 'app.settings.game-options.setting.snooper.label', + defaultMessage: 'Snooper', + }, + extraTelemetryLabel: { + id: 'app.settings.game-options.setting.extra-telemetry.label', + defaultMessage: 'Optional telemetry', + }, + inGameNotificationsLabel: { + id: 'app.settings.game-options.setting.in-game-notifications.label', + defaultMessage: 'In-game notifications', + }, + sharePresenceLabel: { + id: 'app.settings.game-options.setting.share-presence.label', + defaultMessage: 'Share presence', + }, +}) + +const catalogKeyMessages = defineMessages({ + smoothCameraLabel: { + id: 'app.settings.game-options.setting.key-smooth-camera.label', + defaultMessage: 'Toggle cinematic camera', + }, + spectatorOutlinesLabel: { + id: 'app.settings.game-options.setting.key-spectator-outlines.label', + defaultMessage: 'Highlight spectators', + }, + saveToolbarLabel: { + id: 'app.settings.game-options.setting.key-save-toolbar.label', + defaultMessage: 'Save toolbar', + }, + loadToolbarLabel: { + id: 'app.settings.game-options.setting.key-load-toolbar.label', + defaultMessage: 'Load toolbar', + }, + socialInteractionsLabel: { + id: 'app.settings.game-options.setting.key-social-interactions.label', + defaultMessage: 'Social interactions', + }, + quickActionsLabel: { + id: 'app.settings.game-options.setting.key-quick-actions.label', + defaultMessage: 'Quick actions', + }, + spectatorHotbarLabel: { + id: 'app.settings.game-options.setting.key-spectator-hotbar.label', + defaultMessage: 'Spectator hotbar', + }, + friendsLabel: { + id: 'app.settings.game-options.setting.key-friends.label', + defaultMessage: 'Friends', + }, + toggleGuiLabel: { + id: 'app.settings.game-options.setting.key-toggle-gui.label', + defaultMessage: 'Toggle HUD', + }, + toggleSpectatorShaderLabel: { + id: 'app.settings.game-options.setting.key-toggle-spectator-shader.label', + defaultMessage: 'Toggle spectator shader', + }, + hotbar1Label: { + id: 'app.settings.game-options.setting.key-hotbar-1.label', + defaultMessage: 'Hotbar 1', + }, + hotbar2Label: { + id: 'app.settings.game-options.setting.key-hotbar-2.label', + defaultMessage: 'Hotbar 2', + }, + hotbar3Label: { + id: 'app.settings.game-options.setting.key-hotbar-3.label', + defaultMessage: 'Hotbar 3', + }, + hotbar4Label: { + id: 'app.settings.game-options.setting.key-hotbar-4.label', + defaultMessage: 'Hotbar 4', + }, + hotbar5Label: { + id: 'app.settings.game-options.setting.key-hotbar-5.label', + defaultMessage: 'Hotbar 5', + }, + hotbar6Label: { + id: 'app.settings.game-options.setting.key-hotbar-6.label', + defaultMessage: 'Hotbar 6', + }, + hotbar7Label: { + id: 'app.settings.game-options.setting.key-hotbar-7.label', + defaultMessage: 'Hotbar 7', + }, + hotbar8Label: { + id: 'app.settings.game-options.setting.key-hotbar-8.label', + defaultMessage: 'Hotbar 8', + }, + hotbar9Label: { + id: 'app.settings.game-options.setting.key-hotbar-9.label', + defaultMessage: 'Hotbar 9', + }, + debugOverlayLabel: { + id: 'app.settings.game-options.setting.key-debug-overlay.label', + defaultMessage: 'Debug overlay', + }, + debugModifierLabel: { + id: 'app.settings.game-options.setting.key-debug-modifier.label', + defaultMessage: 'Debug modifier', + }, + debugReloadChunksLabel: { + id: 'app.settings.game-options.setting.key-debug-reload-chunks.label', + defaultMessage: 'Reload chunks', + }, + debugHitboxesLabel: { + id: 'app.settings.game-options.setting.key-debug-hitboxes.label', + defaultMessage: 'Show hitboxes', + }, + debugClearChatLabel: { + id: 'app.settings.game-options.setting.key-debug-clear-chat.label', + defaultMessage: 'Clear chat', + }, + debugCrashLabel: { + id: 'app.settings.game-options.setting.key-debug-crash.label', + defaultMessage: 'Trigger debug crash', + }, + debugChunkBordersLabel: { + id: 'app.settings.game-options.setting.key-debug-chunk-borders.label', + defaultMessage: 'Show chunk borders', + }, + debugAdvancedTooltipsLabel: { + id: 'app.settings.game-options.setting.key-debug-advanced-tooltips.label', + defaultMessage: 'Show advanced tooltips', + }, + debugCopyRecreateCommandLabel: { + id: 'app.settings.game-options.setting.key-debug-copy-recreate-command.label', + defaultMessage: 'Copy recreate command', + }, + debugSpectateLabel: { + id: 'app.settings.game-options.setting.key-debug-spectate.label', + defaultMessage: 'Spectate entity', + }, + debugSwitchGameModeLabel: { + id: 'app.settings.game-options.setting.key-debug-switch-game-mode.label', + defaultMessage: 'Switch game mode', + }, + debugOptionsLabel: { + id: 'app.settings.game-options.setting.key-debug-options.label', + defaultMessage: 'Debug options', + }, + debugFocusPauseLabel: { + id: 'app.settings.game-options.setting.key-debug-focus-pause.label', + defaultMessage: 'Pause on lost focus', + }, + debugDumpDynamicTexturesLabel: { + id: 'app.settings.game-options.setting.key-debug-dump-dynamic-textures.label', + defaultMessage: 'Dump dynamic textures', + }, + debugReloadResourcePacksLabel: { + id: 'app.settings.game-options.setting.key-debug-reload-resource-packs.label', + defaultMessage: 'Reload resource packs', + }, + debugProfilingLabel: { + id: 'app.settings.game-options.setting.key-debug-profiling.label', + defaultMessage: 'Start profiling', + }, + debugCopyLocationLabel: { + id: 'app.settings.game-options.setting.key-debug-copy-location.label', + defaultMessage: 'Copy location', + }, + debugDumpVersionLabel: { + id: 'app.settings.game-options.setting.key-debug-dump-version.label', + defaultMessage: 'Dump version', + }, + debugProfilingChartLabel: { + id: 'app.settings.game-options.setting.key-debug-profiling-chart.label', + defaultMessage: 'Profiling chart', + }, + debugFpsChartsLabel: { + id: 'app.settings.game-options.setting.key-debug-fps-charts.label', + defaultMessage: 'FPS charts', + }, + debugNetworkChartsLabel: { + id: 'app.settings.game-options.setting.key-debug-network-charts.label', + defaultMessage: 'Network charts', + }, + debugLightmapTextureLabel: { + id: 'app.settings.game-options.setting.key-debug-lightmap-texture.label', + defaultMessage: 'Lightmap texture', + }, + debugImprovedTransparencyLabel: { + id: 'app.settings.game-options.setting.key-debug-improved-transparency.label', + defaultMessage: 'Improved transparency debug view', + }, +}) + +const categoryMessages = defineMessages({ + skinCustomizationLabel: { + id: 'app.settings.game-options.category.skin-customization.label', + defaultMessage: 'Skin customization', + }, + skinCustomizationDescription: { + id: 'app.settings.game-options.category.skin-customization.description', + defaultMessage: 'Skin layers and main hand', + }, + videoLabel: { id: 'app.settings.game-options.category.video.label', defaultMessage: 'Video' }, + videoDescription: { + id: 'app.settings.game-options.category.video.description', + defaultMessage: 'Camera and display settings', + }, + languageLabel: { + id: 'app.settings.game-options.category.language.label', + defaultMessage: 'Language', + }, + languageDescription: { + id: 'app.settings.game-options.category.language.description', + defaultMessage: 'Game language', + }, + musicAndSoundLabel: { + id: 'app.settings.game-options.category.music-and-sound.label', + defaultMessage: 'Music and sound', + }, + musicAndSoundDescription: { + id: 'app.settings.game-options.category.music-and-sound.description', + defaultMessage: 'Volume and audio preferences', + }, + controlsLabel: { + id: 'app.settings.game-options.category.controls.label', + defaultMessage: 'Controls', + }, + controlsDescription: { + id: 'app.settings.game-options.category.controls.description', + defaultMessage: 'Mouse, movement, and key bindings', + }, + chatLabel: { id: 'app.settings.game-options.category.chat.label', defaultMessage: 'Chat' }, + chatDescription: { + id: 'app.settings.game-options.category.chat.description', + defaultMessage: 'Chat visibility and appearance', + }, + accessibilityLabel: { + id: 'app.settings.game-options.category.accessibility.label', + defaultMessage: 'Accessibility', + }, + accessibilityDescription: { + id: 'app.settings.game-options.category.accessibility.description', + defaultMessage: 'Accessibility preferences', + }, + onlineLabel: { id: 'app.settings.game-options.category.online.label', defaultMessage: 'Online' }, + onlineDescription: { + id: 'app.settings.game-options.category.online.description', + defaultMessage: 'Online and Realms preferences', + }, + customLabel: { + id: 'app.settings.game-options.category.custom.label', + defaultMessage: 'Custom settings', + }, + customDescription: { + id: 'app.settings.game-options.category.custom.description', + defaultMessage: 'Settings added by mods', + }, +}) + +const choiceMessages = defineMessages({ + fast: { id: 'app.settings.game-options.choice.fast', defaultMessage: 'Fast' }, + fancy: { id: 'app.settings.game-options.choice.fancy', defaultMessage: 'Fancy' }, + fabulous: { id: 'app.settings.game-options.choice.fabulous', defaultMessage: 'Fabulous' }, + custom: { id: 'app.settings.game-options.choice.custom', defaultMessage: 'Custom' }, + left: { id: 'app.settings.game-options.choice.left', defaultMessage: 'Left' }, + right: { id: 'app.settings.game-options.choice.right', defaultMessage: 'Right' }, + shown: { id: 'app.settings.game-options.choice.shown', defaultMessage: 'Shown' }, + commandsOnly: { + id: 'app.settings.game-options.choice.commands-only', + defaultMessage: 'Commands only', + }, + hidden: { id: 'app.settings.game-options.choice.hidden', defaultMessage: 'Hidden' }, + all: { id: 'app.settings.game-options.choice.all', defaultMessage: 'All' }, + decreased: { id: 'app.settings.game-options.choice.decreased', defaultMessage: 'Decreased' }, + minimal: { id: 'app.settings.game-options.choice.minimal', defaultMessage: 'Minimal' }, + off: { id: 'app.settings.game-options.choice.off', defaultMessage: 'Off' }, + chat: { id: 'app.settings.game-options.choice.chat', defaultMessage: 'Chat' }, + system: { id: 'app.settings.game-options.choice.system', defaultMessage: 'System' }, + on: { id: 'app.settings.game-options.choice.on', defaultMessage: 'On' }, + minimum: { id: 'app.settings.game-options.choice.minimum', defaultMessage: 'Minimum' }, + maximum: { id: 'app.settings.game-options.choice.maximum', defaultMessage: 'Maximum' }, + never: { id: 'app.settings.game-options.choice.never', defaultMessage: 'Never' }, + pause: { id: 'app.settings.game-options.choice.pause', defaultMessage: 'Pause menu' }, + pauseAndToast: { + id: 'app.settings.game-options.choice.pause-and-toast', + defaultMessage: 'Pause menu and toast', + }, + far: { id: 'app.settings.game-options.choice.far', defaultMessage: 'Far' }, + normal: { id: 'app.settings.game-options.choice.normal', defaultMessage: 'Normal' }, + short: { id: 'app.settings.game-options.choice.short', defaultMessage: 'Short' }, + tiny: { id: 'app.settings.game-options.choice.tiny', defaultMessage: 'Tiny' }, + maxFps: { id: 'app.settings.game-options.choice.max-fps', defaultMessage: 'Max FPS' }, + balanced: { id: 'app.settings.game-options.choice.balanced', defaultMessage: 'Balanced' }, + powerSaver: { id: 'app.settings.game-options.choice.power-saver', defaultMessage: 'Power saver' }, + whileAfk: { id: 'app.settings.game-options.choice.while-afk', defaultMessage: 'While AFK' }, + whenMinimized: { + id: 'app.settings.game-options.choice.when-minimized', + defaultMessage: 'When minimized', + }, + none: { id: 'app.settings.game-options.choice.none', defaultMessage: 'None' }, + byPlayer: { id: 'app.settings.game-options.choice.by-player', defaultMessage: 'By player' }, + nearby: { id: 'app.settings.game-options.choice.nearby', defaultMessage: 'Nearby' }, + crosshair: { id: 'app.settings.game-options.choice.crosshair', defaultMessage: 'Crosshair' }, + hotbar: { id: 'app.settings.game-options.choice.hotbar', defaultMessage: 'Hotbar' }, + constant: { id: 'app.settings.game-options.choice.constant', defaultMessage: 'Constant' }, + default: { id: 'app.settings.game-options.choice.default', defaultMessage: 'Default' }, + frequent: { id: 'app.settings.game-options.choice.frequent', defaultMessage: 'Frequent' }, + limited: { id: 'app.settings.game-options.choice.limited', defaultMessage: 'Limited' }, + openGl: { id: 'app.settings.game-options.choice.opengl', defaultMessage: 'OpenGL' }, + vulkan: { id: 'app.settings.game-options.choice.vulkan', defaultMessage: 'Vulkan' }, +}) + +export const presentationMessages = defineMessages({ + customValuePlaceholder: { + id: 'app.settings.game-options.custom-value.placeholder', + defaultMessage: 'Enter a value', + }, + validationMissingValue: { + id: 'app.settings.game-options.validation.missing-value', + defaultMessage: 'Choose a value first.', + }, + validationNoCompatibleInstances: { + id: 'app.settings.game-options.validation.no-compatible-instances', + defaultMessage: 'Some of your instances cannot use this setting', + }, + validationInvalidValue: { + id: 'app.settings.game-options.validation.invalid-value', + defaultMessage: 'Choose a valid value.', + }, + validationChangedSinceOpened: { + id: 'app.settings.game-options.validation.changed-since-opened', + defaultMessage: 'This setting changed elsewhere. Check it and try again.', + }, + compatibilityNone: { + id: 'app.settings.game-options.compatibility.none', + defaultMessage: 'Some of your instances cannot use this setting', + }, + bucketLauncherControlled: { + id: 'app.settings.game-options.compatibility.reason.launcher-controlled', + defaultMessage: 'This setting is managed by Modrinth’s launch settings.', + }, +}) + +const knownSettings: Record = + { + fov: { label: settingMessages.fovLabel }, + graphics: { + label: settingMessages.graphicsLabel, + description: settingMessages.graphicsDescription, + }, + ambient_occlusion: { label: settingMessages.ambientOcclusionLabel }, + render_distance: { label: settingMessages.renderDistanceLabel }, + simulation_distance: { + label: settingMessages.simulationDistanceLabel, + description: settingMessages.simulationDistanceDescription, + }, + gui_scale: { + label: settingMessages.guiScaleLabel, + description: settingMessages.guiScaleDescription, + }, + particles: { label: settingMessages.particlesLabel }, + clouds: { label: settingMessages.cloudsLabel }, + entity_shadows: { label: settingMessages.entityShadowsLabel }, + view_bobbing: { + label: settingMessages.viewBobbingLabel, + description: settingMessages.viewBobbingDescription, + }, + vsync: { label: settingMessages.vsyncLabel, description: settingMessages.vsyncDescription }, + fullscreen: { label: settingMessages.fullscreenLabel }, + max_framerate: { label: settingMessages.maxFramerateLabel }, + mipmap_levels: { + label: settingMessages.mipmapLevelsLabel, + description: settingMessages.mipmapLevelsDescription, + }, + biome_blend_radius: { + label: settingMessages.biomeBlendRadiusLabel, + description: settingMessages.biomeBlendRadiusDescription, + }, + language: { label: settingMessages.languageLabel }, + master_volume: { label: settingMessages.masterVolumeLabel }, + music_volume: { label: settingMessages.musicVolumeLabel }, + music_toast: { + label: settingMessages.musicToastLabel, + description: settingMessages.musicToastDescription, + }, + record_volume: { label: settingMessages.recordVolumeLabel }, + weather_volume: { label: settingMessages.weatherVolumeLabel }, + blocks_volume: { label: settingMessages.blocksVolumeLabel }, + hostile_volume: { label: settingMessages.hostileVolumeLabel }, + neutral_volume: { label: settingMessages.neutralVolumeLabel }, + players_volume: { label: settingMessages.playersVolumeLabel }, + ambient_volume: { label: settingMessages.ambientVolumeLabel }, + voice_volume: { label: settingMessages.voiceVolumeLabel }, + ui_volume: { label: settingMessages.uiVolumeLabel }, + sensitivity: { label: settingMessages.sensitivityLabel }, + invert_mouse: { + label: settingMessages.invertMouseLabel, + description: settingMessages.invertMouseDescription, + }, + auto_jump: { + label: settingMessages.autoJumpLabel, + description: settingMessages.autoJumpDescription, + }, + toggle_crouch: { + label: settingMessages.toggleCrouchLabel, + description: settingMessages.toggleCrouchDescription, + }, + toggle_sprint: { + label: settingMessages.toggleSprintLabel, + description: settingMessages.toggleSprintDescription, + }, + discrete_mouse_scroll: { + label: settingMessages.discreteMouseScrollLabel, + description: settingMessages.discreteMouseScrollDescription, + }, + 'key.forward': { label: settingMessages.keyForwardLabel }, + 'key.left': { label: settingMessages.keyLeftLabel }, + 'key.back': { label: settingMessages.keyBackLabel }, + 'key.right': { label: settingMessages.keyRightLabel }, + 'key.jump': { label: settingMessages.keyJumpLabel }, + 'key.sneak': { label: settingMessages.keySneakLabel }, + 'key.sprint': { label: settingMessages.keySprintLabel }, + 'key.inventory': { label: settingMessages.keyInventoryLabel }, + 'key.swap_offhand': { label: settingMessages.keySwapOffhandLabel }, + 'key.drop': { label: settingMessages.keyDropLabel }, + 'key.use': { label: settingMessages.keyUseLabel }, + 'key.attack': { label: settingMessages.keyAttackLabel }, + 'key.pick_item': { label: settingMessages.keyPickItemLabel }, + 'key.chat': { label: settingMessages.keyChatLabel }, + 'key.player_list': { label: settingMessages.keyPlayerListLabel }, + 'key.command': { label: settingMessages.keyCommandLabel }, + 'key.screenshot': { label: settingMessages.keyScreenshotLabel }, + 'key.perspective': { label: settingMessages.keyPerspectiveLabel }, + 'key.fullscreen': { label: settingMessages.keyFullscreenLabel }, + 'key.advancements': { label: settingMessages.keyAdvancementsLabel }, + chat_visibility: { label: settingMessages.chatVisibilityLabel }, + chat_colors: { label: settingMessages.chatColorsLabel }, + chat_links: { + label: settingMessages.chatLinksLabel, + description: settingMessages.chatLinksDescription, + }, + chat_links_prompt: { + label: settingMessages.chatLinksPromptLabel, + description: settingMessages.chatLinksPromptDescription, + }, + chat_opacity: { + label: settingMessages.chatOpacityLabel, + description: settingMessages.chatOpacityDescription, + }, + chat_scale: { label: settingMessages.chatScaleLabel }, + narrator: { + label: settingMessages.narratorLabel, + description: settingMessages.narratorDescription, + }, + subtitles: { + label: settingMessages.subtitlesLabel, + description: settingMessages.subtitlesDescription, + }, + high_contrast: { + label: settingMessages.highContrastLabel, + description: settingMessages.highContrastDescription, + }, + dark_splash: { + label: settingMessages.darkSplashLabel, + description: settingMessages.darkSplashDescription, + }, + notification_time: { + label: settingMessages.notificationTimeLabel, + description: settingMessages.notificationTimeDescription, + }, + main_hand: { + label: settingMessages.mainHandLabel, + description: settingMessages.mainHandDescription, + }, + cape: { label: settingMessages.capeLabel, description: settingMessages.capeDescription }, + hat: { label: settingMessages.hatLabel, description: settingMessages.hatDescription }, + jacket: { label: settingMessages.jacketLabel, description: settingMessages.jacketDescription }, + allow_server_listing: { + label: settingMessages.allowServerListingLabel, + description: settingMessages.allowServerListingDescription, + }, + realms_notifications: { label: settingMessages.realmsNotificationsLabel }, + brightness: { label: catalogSettingMessages.brightnessLabel }, + legacy_view_distance: { label: catalogSettingMessages.legacyViewDistanceLabel }, + entity_distance: { label: catalogSettingMessages.entityDistanceLabel }, + debug_gui_scale: { label: catalogSettingMessages.debugGuiScaleLabel }, + graphics_backend: { label: catalogSettingMessages.graphicsBackendLabel }, + cloud_range: { label: catalogSettingMessages.cloudRangeLabel }, + exclusive_fullscreen: { label: catalogSettingMessages.exclusiveFullscreenLabel }, + mac_fullscreen_menu: { label: catalogSettingMessages.macFullscreenMenuLabel }, + legacy_framerate_limit: { label: catalogSettingMessages.legacyFramerateLimitLabel }, + inactivity_framerate_limit: { + label: catalogSettingMessages.inactivityFramerateLimitLabel, + }, + prioritize_chunk_updates: { label: catalogSettingMessages.prioritizeChunkUpdatesLabel }, + attack_indicator: { label: catalogSettingMessages.attackIndicatorLabel }, + reduced_debug_info: { label: catalogSettingMessages.reducedDebugInfoLabel }, + chunk_fade_time: { label: catalogSettingMessages.chunkFadeTimeLabel }, + cutout_leaves: { label: catalogSettingMessages.cutoutLeavesLabel }, + improved_transparency: { label: catalogSettingMessages.improvedTransparencyLabel }, + texture_filtering: { label: catalogSettingMessages.textureFilteringLabel }, + anisotropy: { label: catalogSettingMessages.anisotropyLabel }, + vignette: { label: catalogSettingMessages.vignetteLabel }, + weather_radius: { label: catalogSettingMessages.weatherRadiusLabel }, + advanced_opengl: { label: catalogSettingMessages.advancedOpenGlLabel }, + anaglyph_3d: { label: catalogSettingMessages.anaglyph3dLabel }, + anisotropic_filtering: { label: catalogSettingMessages.anisotropicFilteringLabel }, + alternate_blocks: { label: catalogSettingMessages.alternateBlocksLabel }, + held_item_tooltips: { label: catalogSettingMessages.heldItemTooltipsLabel }, + use_vbo: { label: catalogSettingMessages.useVboLabel }, + force_unicode_font: { label: catalogSettingMessages.forceUnicodeFontLabel }, + japanese_glyph_variants: { label: catalogSettingMessages.japaneseGlyphVariantsLabel }, + music_frequency: { label: catalogSettingMessages.musicFrequencyLabel }, + directional_audio: { label: catalogSettingMessages.directionalAudioLabel }, + invert_horizontal_mouse: { label: catalogSettingMessages.invertHorizontalMouseLabel }, + toggle_attack: { label: catalogSettingMessages.toggleAttackLabel }, + toggle_use: { label: catalogSettingMessages.toggleUseLabel }, + mouse_wheel_sensitivity: { label: catalogSettingMessages.mouseWheelSensitivityLabel }, + raw_mouse_input: { label: catalogSettingMessages.rawMouseInputLabel }, + touchscreen: { label: catalogSettingMessages.touchscreenLabel }, + allow_cursor_changes: { label: catalogSettingMessages.allowCursorChangesLabel }, + sprint_window: { label: catalogSettingMessages.sprintWindowLabel }, + operator_items_tab: { label: catalogSettingMessages.operatorItemsTabLabel }, + ctrl_click_right_click: { label: catalogSettingMessages.ctrlClickRightClickLabel }, + quit_shortcuts: { label: catalogSettingMessages.quitShortcutsLabel }, + chat_width: { label: catalogSettingMessages.chatWidthLabel }, + focused_chat_height: { label: catalogSettingMessages.focusedChatHeightLabel }, + unfocused_chat_height: { label: catalogSettingMessages.unfocusedChatHeightLabel }, + chat_line_spacing: { label: catalogSettingMessages.chatLineSpacingLabel }, + chat_delay: { label: catalogSettingMessages.chatDelayLabel }, + text_background_opacity: { label: catalogSettingMessages.textBackgroundOpacityLabel }, + chat_background_only: { label: catalogSettingMessages.chatBackgroundOnlyLabel }, + auto_suggestions: { label: catalogSettingMessages.autoSuggestionsLabel }, + secure_chat_only: { label: catalogSettingMessages.secureChatOnlyLabel }, + save_chat_drafts: { label: catalogSettingMessages.saveChatDraftsLabel }, + hide_matched_names: { label: catalogSettingMessages.hideMatchedNamesLabel }, + chat_preview: { label: catalogSettingMessages.chatPreviewLabel }, + fov_effects: { label: catalogSettingMessages.fovEffectsLabel }, + screen_effects: { label: catalogSettingMessages.screenEffectsLabel }, + darkness_pulsing: { label: catalogSettingMessages.darknessPulsingLabel }, + damage_tilt: { label: catalogSettingMessages.damageTiltLabel }, + glint_speed: { label: catalogSettingMessages.glintSpeedLabel }, + glint_strength: { label: catalogSettingMessages.glintStrengthLabel }, + hide_lightning_flashes: { label: catalogSettingMessages.hideLightningFlashesLabel }, + hide_splash_texts: { label: catalogSettingMessages.hideSplashTextsLabel }, + high_contrast_outline: { label: catalogSettingMessages.highContrastOutlineLabel }, + narrator_hotkey: { label: catalogSettingMessages.narratorHotkeyLabel }, + autosave_indicator: { label: catalogSettingMessages.autosaveIndicatorLabel }, + panorama_speed: { label: catalogSettingMessages.panoramaSpeedLabel }, + menu_background_blur: { label: catalogSettingMessages.menuBackgroundBlurLabel }, + rotate_with_minecart: { label: catalogSettingMessages.rotateWithMinecartLabel }, + left_sleeve: { label: catalogSettingMessages.leftSleeveLabel }, + right_sleeve: { label: catalogSettingMessages.rightSleeveLabel }, + left_pants_leg: { label: catalogSettingMessages.leftPantsLegLabel }, + right_pants_leg: { label: catalogSettingMessages.rightPantsLegLabel }, + hide_server_address: { label: catalogSettingMessages.hideServerAddressLabel }, + server_textures: { label: catalogSettingMessages.serverTexturesLabel }, + snooper: { label: catalogSettingMessages.snooperLabel }, + extra_telemetry: { label: catalogSettingMessages.extraTelemetryLabel }, + in_game_notifications: { label: catalogSettingMessages.inGameNotificationsLabel }, + share_presence: { label: catalogSettingMessages.sharePresenceLabel }, + 'key.smooth_camera': { label: catalogKeyMessages.smoothCameraLabel }, + 'key.spectator_outlines': { label: catalogKeyMessages.spectatorOutlinesLabel }, + 'key.save_toolbar': { label: catalogKeyMessages.saveToolbarLabel }, + 'key.load_toolbar': { label: catalogKeyMessages.loadToolbarLabel }, + 'key.social_interactions': { label: catalogKeyMessages.socialInteractionsLabel }, + 'key.quick_actions': { label: catalogKeyMessages.quickActionsLabel }, + 'key.spectator_hotbar': { label: catalogKeyMessages.spectatorHotbarLabel }, + 'key.friends': { label: catalogKeyMessages.friendsLabel }, + 'key.toggle_gui': { label: catalogKeyMessages.toggleGuiLabel }, + 'key.toggle_spectator_shader': { label: catalogKeyMessages.toggleSpectatorShaderLabel }, + 'key.hotbar.1': { label: catalogKeyMessages.hotbar1Label }, + 'key.hotbar.2': { label: catalogKeyMessages.hotbar2Label }, + 'key.hotbar.3': { label: catalogKeyMessages.hotbar3Label }, + 'key.hotbar.4': { label: catalogKeyMessages.hotbar4Label }, + 'key.hotbar.5': { label: catalogKeyMessages.hotbar5Label }, + 'key.hotbar.6': { label: catalogKeyMessages.hotbar6Label }, + 'key.hotbar.7': { label: catalogKeyMessages.hotbar7Label }, + 'key.hotbar.8': { label: catalogKeyMessages.hotbar8Label }, + 'key.hotbar.9': { label: catalogKeyMessages.hotbar9Label }, + 'key.debug.overlay': { label: catalogKeyMessages.debugOverlayLabel }, + 'key.debug.modifier': { label: catalogKeyMessages.debugModifierLabel }, + 'key.debug.reload_chunks': { label: catalogKeyMessages.debugReloadChunksLabel }, + 'key.debug.hitboxes': { label: catalogKeyMessages.debugHitboxesLabel }, + 'key.debug.clear_chat': { label: catalogKeyMessages.debugClearChatLabel }, + 'key.debug.crash': { label: catalogKeyMessages.debugCrashLabel }, + 'key.debug.chunk_borders': { label: catalogKeyMessages.debugChunkBordersLabel }, + 'key.debug.advanced_tooltips': { + label: catalogKeyMessages.debugAdvancedTooltipsLabel, + }, + 'key.debug.copy_recreate_command': { + label: catalogKeyMessages.debugCopyRecreateCommandLabel, + }, + 'key.debug.spectate': { label: catalogKeyMessages.debugSpectateLabel }, + 'key.debug.switch_game_mode': { label: catalogKeyMessages.debugSwitchGameModeLabel }, + 'key.debug.options': { label: catalogKeyMessages.debugOptionsLabel }, + 'key.debug.focus_pause': { label: catalogKeyMessages.debugFocusPauseLabel }, + 'key.debug.dump_dynamic_textures': { + label: catalogKeyMessages.debugDumpDynamicTexturesLabel, + }, + 'key.debug.reload_resource_packs': { + label: catalogKeyMessages.debugReloadResourcePacksLabel, + }, + 'key.debug.profiling': { label: catalogKeyMessages.debugProfilingLabel }, + 'key.debug.copy_location': { label: catalogKeyMessages.debugCopyLocationLabel }, + 'key.debug.dump_version': { label: catalogKeyMessages.debugDumpVersionLabel }, + 'key.debug.profiling_chart': { label: catalogKeyMessages.debugProfilingChartLabel }, + 'key.debug.fps_charts': { label: catalogKeyMessages.debugFpsChartsLabel }, + 'key.debug.network_charts': { label: catalogKeyMessages.debugNetworkChartsLabel }, + 'key.debug.lightmap_texture': { label: catalogKeyMessages.debugLightmapTextureLabel }, + 'key.debug.improved_transparency': { + label: catalogKeyMessages.debugImprovedTransparencyLabel, + }, + } + +const categories: Record = { + skin_customization: { + label: categoryMessages.skinCustomizationLabel, + description: categoryMessages.skinCustomizationDescription, + }, + video: { label: categoryMessages.videoLabel, description: categoryMessages.videoDescription }, + video_settings: { + label: categoryMessages.videoLabel, + description: categoryMessages.videoDescription, + }, + language: { + label: categoryMessages.languageLabel, + description: categoryMessages.languageDescription, + }, + music_and_sound: { + label: categoryMessages.musicAndSoundLabel, + description: categoryMessages.musicAndSoundDescription, + }, + controls: { + label: categoryMessages.controlsLabel, + description: categoryMessages.controlsDescription, + }, + chat: { label: categoryMessages.chatLabel, description: categoryMessages.chatDescription }, + chat_settings: { + label: categoryMessages.chatLabel, + description: categoryMessages.chatDescription, + }, + accessibility: { + label: categoryMessages.accessibilityLabel, + description: categoryMessages.accessibilityDescription, + }, + online: { label: categoryMessages.onlineLabel, description: categoryMessages.onlineDescription }, + custom: { label: categoryMessages.customLabel, description: categoryMessages.customDescription }, + custom_settings: { + label: categoryMessages.customLabel, + description: categoryMessages.customDescription, + }, +} + +const choices: Record = { + 'graphics:fast': choiceMessages.fast, + 'graphics:fancy': choiceMessages.fancy, + 'graphics:fabulous': choiceMessages.fabulous, + 'graphics:custom': choiceMessages.custom, + 'main_hand:left': choiceMessages.left, + 'main_hand:right': choiceMessages.right, + 'chat_visibility:0': choiceMessages.shown, + 'chat_visibility:1': choiceMessages.commandsOnly, + 'chat_visibility:2': choiceMessages.hidden, + 'particles:0': choiceMessages.all, + 'particles:1': choiceMessages.decreased, + 'particles:2': choiceMessages.minimal, + 'narrator:0': choiceMessages.off, + 'narrator:1': choiceMessages.all, + 'narrator:2': choiceMessages.chat, + 'narrator:3': choiceMessages.system, + 'clouds:false': choiceMessages.off, + 'clouds:fast': choiceMessages.fast, + 'clouds:true': choiceMessages.fancy, + 'ambient_occlusion:off': choiceMessages.off, + 'ambient_occlusion:on': choiceMessages.on, + 'ambient_occlusion:minimum': choiceMessages.minimum, + 'ambient_occlusion:maximum': choiceMessages.maximum, + 'music_toast:never': choiceMessages.never, + 'music_toast:pause': choiceMessages.pause, + 'music_toast:pause_and_toast': choiceMessages.pauseAndToast, + 'legacy_view_distance:0': choiceMessages.far, + 'legacy_view_distance:1': choiceMessages.normal, + 'legacy_view_distance:2': choiceMessages.short, + 'legacy_view_distance:3': choiceMessages.tiny, + 'legacy_framerate_limit:0': choiceMessages.maxFps, + 'legacy_framerate_limit:1': choiceMessages.balanced, + 'legacy_framerate_limit:2': choiceMessages.powerSaver, + 'inactivity_framerate_limit:afk': choiceMessages.whileAfk, + 'inactivity_framerate_limit:minimized': choiceMessages.whenMinimized, + 'prioritize_chunk_updates:0': choiceMessages.none, + 'prioritize_chunk_updates:1': choiceMessages.byPlayer, + 'prioritize_chunk_updates:2': choiceMessages.nearby, + 'attack_indicator:0': choiceMessages.off, + 'attack_indicator:1': choiceMessages.crosshair, + 'attack_indicator:2': choiceMessages.hotbar, + 'chat_preview:0': choiceMessages.off, + 'chat_preview:1': choiceMessages.commandsOnly, + 'chat_preview:2': choiceMessages.on, + 'music_frequency:CONSTANT': choiceMessages.constant, + 'music_frequency:DEFAULT': choiceMessages.default, + 'music_frequency:FREQUENT': choiceMessages.frequent, + 'share_presence:all': choiceMessages.all, + 'share_presence:limited': choiceMessages.limited, + 'share_presence:none': choiceMessages.none, + 'graphics_backend:default': choiceMessages.default, + 'graphics_backend:opengl': choiceMessages.openGl, + 'graphics_backend:vulkan': choiceMessages.vulkan, +} + +const validationMessages: Record = { + missing_value: presentationMessages.validationMissingValue, + no_compatible_instances: presentationMessages.validationNoCompatibleInstances, + invalid_value: presentationMessages.validationInvalidValue, + changed_since_opened: presentationMessages.validationChangedSinceOpened, +} + +export function formatGameSettingLabel( + formatMessage: FormatMessage, + setting: EditableGameSetting, +): string { + if (setting.kind === 'external') return setting.raw_key ?? setting.option_id + const definition = knownSettings[setting.option_id] + return definition ? formatMessage(definition.label) : setting.option_id +} + +export function formatGameSettingDescription( + formatMessage: FormatMessage, + setting: EditableGameSetting, +): string { + if (setting.kind === 'external') return '' + const definition = knownSettings[setting.option_id] + return definition?.description ? formatMessage(definition.description) : '' +} + +export function gameSettingCategoryMessage(category: GameSettingCategory): MessageDescriptor { + return ( + categories[category.id]?.label ?? { + id: `app.settings.game-options.category.${category.id}.label`, + defaultMessage: category.id, + } + ) +} + +export function formatGameSettingChoice( + formatMessage: FormatMessage, + optionId: string, + value: string, +): string { + const message = choices[`${optionId}:${value}`] + return message ? formatMessage(message) : value +} + +export function formatGameSettingValidation( + formatMessage: FormatMessage, + error: GameOptionValidationError | null | undefined, +): string | null { + return error ? formatMessage(validationMessages[error]) : null +} diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/row.vue b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/row.vue new file mode 100644 index 0000000..6586b17 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/row.vue @@ -0,0 +1,384 @@ + + + diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-editor.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-editor.ts new file mode 100644 index 0000000..9446de5 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-editor.ts @@ -0,0 +1,448 @@ +import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui' +import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' +import { computed, type MaybeRefOrGetter, nextTick, onScopeDispose, ref, toValue, watch } from 'vue' + +import { + type EditableGameSetting, + type GameOptionCanonicalValue, + type GameSettingChange, + type GameSettingsEditorState, + gameSettingsKeys, + gameSettingsQueryOptions, + get_local_game_options_config, + get_synced_game_options_config, + preview_local_game_option_changes, + preview_synced_game_option_changes, + save_local_game_option_changes, + save_synced_game_option_changes, + type UpdateGameSettingsRequest, +} from '@/helpers/game-options' + +import { canonicalValuesEqual, cloneGameSettingsState, gameSettingChanges } from './editors' + +export function useGameSettingsEditor( + instanceId: MaybeRefOrGetter, + onSaved: () => void, +) { + const { addNotification, handleError } = injectNotificationManager() + const { formatMessage } = useVIntl() + const queryClient = useQueryClient() + const messages = defineMessages({ + conflictTitle: { + id: 'app.settings.synced-options.game-settings.conflict-title', + defaultMessage: 'These settings changed elsewhere', + }, + conflictText: { + id: 'app.settings.synced-options.game-settings.conflict-text', + defaultMessage: 'We loaded the latest settings. Check your changes, then save again.', + }, + }) + + const baseState = ref(null) + const draftState = ref(null) + const touchedValueOptionIds = ref>(new Set()) + let previewTimer: ReturnType | null = null + let previewGeneration = 0 + let loadGeneration = 0 + let saveGeneration = 0 + + const editorInstanceId = ref() + const isLocalEditor = computed(() => !!editorInstanceId.value) + + const dirtyChanges = computed(() => + gameSettingChanges(baseState.value, draftState.value, touchedValueOptionIds.value), + ) + const isDirty = computed(() => dirtyChanges.value.length > 0) + const dirtyOptionIds = computed( + () => new Set(dirtyChanges.value.map((change) => change.option_id)), + ) + const hasBlockingDraft = computed( + () => + draftState.value?.settings.some( + (setting) => + dirtyOptionIds.value.has(setting.option_id) && + (isLocalEditor.value || setting.sync_enabled) && + (!!setting.validation_error || + ['mixed', 'unset', 'invalid'].includes(setting.value_state)), + ) ?? false, + ) + const stateQuery = useQuery( + computed(() => ({ + ...gameSettingsQueryOptions(editorInstanceId.value), + enabled: false, + })), + ) + const loading = computed( + () => !draftState.value && (stateQuery.isPending.value || stateQuery.isFetching.value), + ) + const loadError = computed(() => !draftState.value && stateQuery.isError.value) + const previewMutation = useMutation({ + mutationFn: ({ + instanceId, + request, + }: { + instanceId?: string + request: UpdateGameSettingsRequest + }) => + instanceId + ? preview_local_game_option_changes(instanceId, request) + : preview_synced_game_option_changes(request), + }) + const saveMutation = useMutation({ + mutationFn: saveDraft, + onError: handleError, + }) + + function applyState(state: GameSettingsEditorState) { + baseState.value = cloneGameSettingsState(state) + draftState.value = cloneGameSettingsState(state) + touchedValueOptionIds.value = new Set() + } + + watch(stateQuery.data, (state) => { + if (state && draftState.value && !isDirty.value && !saveMutation.isPending.value) { + applyState(state) + } + }) + + async function load() { + const generation = ++loadGeneration + cancelPreview() + editorInstanceId.value = toValue(instanceId) + const options = gameSettingsQueryOptions(editorInstanceId.value) + const cached = !editorInstanceId.value && queryClient.getQueryData(options.queryKey) + if (cached) { + applyState(cached) + try { + // A previously cached empty state is common immediately after enabling + // sync. Refresh it before returning so the editor does not stay empty. + const refreshed = await queryClient.fetchQuery({ ...options, staleTime: 0 }) + if (generation !== loadGeneration) return false + applyState(refreshed) + return true + } catch (error) { + if (generation !== loadGeneration) return false + handleError(error) + return true + } + } + await nextTick() + const result = await queryClient + .fetchQuery({ ...options, staleTime: 0 }) + .then((data) => ({ data, isError: false, error: null as unknown })) + .catch((error) => ({ data: undefined, isError: true, error })) + if (generation !== loadGeneration) return false + if (result.isError) { + handleError(result.error) + return false + } + if (!result.data) return false + applyState(result.data) + return true + } + + function cancelPreview() { + if (previewTimer) clearTimeout(previewTimer) + previewTimer = null + previewGeneration++ + } + + function reset() { + cancelPreview() + loadGeneration++ + saveGeneration++ + baseState.value = null + draftState.value = null + touchedValueOptionIds.value = new Set() + saveMutation.reset() + } + + function cancelChanges() { + if (!baseState.value) return + cancelPreview() + draftState.value = cloneGameSettingsState(baseState.value) + touchedValueOptionIds.value = new Set() + } + + function setSyncEnabled(optionIds: readonly string[], enabled: boolean) { + if (!draftState.value) return + const ids = new Set(optionIds) + draftState.value.settings = draftState.value.settings.map((setting) => + ids.has(setting.option_id) + ? { ...setting, sync_enabled: enabled, validation_error: null } + : setting, + ) + schedulePreview() + } + + function schedulePreview() { + cancelPreview() + previewTimer = setTimeout(() => void preview(), 350) + } + + function save() { + if (saveMutation.isPending.value || loading.value || hasBlockingDraft.value || !isDirty.value) + return + saveMutation.mutate() + } + + async function saveAndWait(): Promise { + if (loading.value || hasBlockingDraft.value || !isDirty.value) return !hasBlockingDraft.value + try { + await saveMutation.mutateAsync() + return true + } catch { + return false + } + } + + function editorRequest(): UpdateGameSettingsRequest | null { + if (!baseState.value || !draftState.value) return null + return { + expected_summary_revision: draftState.value.summary_revision, + expected_canonical_revision: draftState.value.canonical_revision, + expected_catalog_revision: draftState.value.catalog_revision, + changes: dirtyChanges.value, + } + } + + function setCanonicalValue(optionId: string, value: GameOptionCanonicalValue | null) { + if (!draftState.value) return + const currentSetting = draftState.value.settings.find( + (setting) => setting.option_id === optionId, + ) + const baseSetting = baseState.value?.settings.find((setting) => setting.option_id === optionId) + if (!currentSetting) return + + const revertedToBase = + !!baseSetting && + !canonicalValuesEqual(currentSetting.canonical_value, baseSetting.canonical_value) && + canonicalValuesEqual(value, baseSetting.canonical_value) + const touchedOptionIds = new Set(touchedValueOptionIds.value) + if (revertedToBase) { + touchedOptionIds.delete(optionId) + } else { + touchedOptionIds.add(optionId) + } + touchedValueOptionIds.value = touchedOptionIds + draftState.value.settings = draftState.value.settings.map((setting) => + setting.option_id === optionId + ? { + ...setting, + canonical_value: value, + value_state: revertedToBase ? baseSetting.value_state : value ? 'canonical' : 'unset', + validation_error: revertedToBase ? baseSetting.validation_error : null, + } + : setting, + ) + schedulePreview() + } + + function mergePreview(preview: GameSettingsEditorState) { + if (!baseState.value || !draftState.value) return + const previousBase = cloneGameSettingsState(baseState.value) + const previousDraft = cloneGameSettingsState(draftState.value) + const dirtyIds = new Set( + gameSettingChanges(previousBase, previousDraft, touchedValueOptionIds.value).map( + (change) => change.option_id, + ), + ) + const stagedSettings = new Map( + previousDraft.settings + .filter((setting) => dirtyIds.has(setting.option_id)) + .map((setting) => [setting.option_id, setting]), + ) + const previousBaseSettings = new Map( + previousBase.settings.map((setting) => [setting.option_id, setting]), + ) + const nextBase = cloneGameSettingsState(preview) + const nextDraft = cloneGameSettingsState(preview) + nextBase.settings = nextBase.settings.map((setting) => + dirtyIds.has(setting.option_id) + ? { ...(previousBaseSettings.get(setting.option_id) ?? setting) } + : setting, + ) + nextDraft.settings = nextDraft.settings.map((setting): EditableGameSetting => { + const staged = stagedSettings.get(setting.option_id) + if (!staged) return setting + return { + ...setting, + sync_enabled: staged.sync_enabled, + canonical_value: staged.canonical_value, + option_revision: staged.option_revision, + } + }) + baseState.value = nextBase + draftState.value = nextDraft + } + + async function preview() { + const request = editorRequest() + if (!request || request.changes.length === 0) { + previewGeneration++ + if (baseState.value) draftState.value = cloneGameSettingsState(baseState.value) + return + } + + const generation = ++previewGeneration + try { + const previewState = await previewMutation.mutateAsync({ + instanceId: editorInstanceId.value, + request, + }) + if (generation !== previewGeneration) return + mergePreview(previewState) + } catch { + return + } + } + + function applyChangesToRefreshedState( + refreshed: GameSettingsEditorState, + stagedDraft: GameSettingsEditorState, + changes: GameSettingChange[], + touchedOptionIds: ReadonlySet, + conflictOptionIds: ReadonlySet = new Set(), + ) { + const changesById = new Map(changes.map((change) => [change.option_id, change])) + const stagedSettings = new Map( + stagedDraft.settings.map((setting) => [setting.option_id, setting]), + ) + const nextBase = cloneGameSettingsState(refreshed) + const nextDraft = cloneGameSettingsState(refreshed) + + nextDraft.settings = nextDraft.settings.map((setting) => { + const change = changesById.get(setting.option_id) + const staged = stagedSettings.get(setting.option_id) + if (!change || !staged) return setting + + return { + ...setting, + ...(change.sync_enabled !== undefined ? { sync_enabled: staged.sync_enabled } : {}), + ...(change.canonical_value !== undefined + ? { + canonical_value: staged.canonical_value, + value_state: staged.value_state, + } + : {}), + validation_error: conflictOptionIds.has(setting.option_id) + ? 'changed_since_opened' + : staged.validation_error, + } + }) + + baseState.value = nextBase + draftState.value = nextDraft + touchedValueOptionIds.value = new Set( + [...touchedOptionIds].filter( + (optionId) => changesById.get(optionId)?.canonical_value !== undefined, + ), + ) + } + + async function saveDraft() { + const request = editorRequest() + if (!request || request.changes.length === 0 || !baseState.value || !draftState.value) return + + cancelPreview() + const targetInstanceId = editorInstanceId.value + const generation = ++saveGeneration + const previousBase = cloneGameSettingsState(baseState.value) + const optimisticState = cloneGameSettingsState(draftState.value) + const previouslyTouchedOptionIds = new Set(touchedValueOptionIds.value) + baseState.value = cloneGameSettingsState(optimisticState) + touchedValueOptionIds.value = new Set() + try { + await queryClient.cancelQueries({ + queryKey: gameSettingsQueryOptions(targetInstanceId).queryKey, + }) + const result = targetInstanceId + ? await save_local_game_option_changes(targetInstanceId, request) + : await save_synced_game_option_changes(request) + const refreshed = + result.state ?? + (targetInstanceId + ? await get_local_game_options_config(targetInstanceId) + : await get_synced_game_options_config()) + queryClient.setQueryData(gameSettingsQueryOptions(targetInstanceId).queryKey, refreshed) + if (targetInstanceId) { + void queryClient.invalidateQueries({ queryKey: gameSettingsKeys.synced }) + } + if (result.conflicts?.length) { + if (generation !== saveGeneration || !draftState.value) return + previewGeneration++ + const stagedDraft = cloneGameSettingsState(draftState.value) + const retainedTouchedOptionIds = new Set([ + ...previouslyTouchedOptionIds, + ...touchedValueOptionIds.value, + ]) + const stagedChanges = gameSettingChanges( + previousBase, + stagedDraft, + retainedTouchedOptionIds, + ) + const conflicts = new Set(result.conflicts) + applyChangesToRefreshedState( + refreshed, + stagedDraft, + stagedChanges, + retainedTouchedOptionIds, + conflicts, + ) + + addNotification({ + type: 'warning', + title: formatMessage(messages.conflictTitle), + text: formatMessage(messages.conflictText), + }) + if (isDirty.value) { + schedulePreview() + } + return + } + + if (generation !== saveGeneration) { + onSaved() + return + } + if (!baseState.value || !draftState.value) return + previewGeneration++ + const stagedDraft = cloneGameSettingsState(draftState.value) + const touchedSinceSave = new Set(touchedValueOptionIds.value) + const changesSinceSave = gameSettingChanges(optimisticState, stagedDraft, touchedSinceSave) + applyChangesToRefreshedState(refreshed, stagedDraft, changesSinceSave, touchedSinceSave) + if (changesSinceSave.length > 0) schedulePreview() + onSaved() + } catch (error) { + if (generation === saveGeneration && draftState.value) { + previewGeneration++ + baseState.value = previousBase + touchedValueOptionIds.value = new Set([ + ...previouslyTouchedOptionIds, + ...touchedValueOptionIds.value, + ]) + if (dirtyChanges.value.length > 0) schedulePreview() + } + throw error + } + } + + onScopeDispose(reset) + + return { + draftState, + isLocalEditor, + isDirty, + hasBlockingDraft, + loading, + loadError, + saving: saveMutation.isPending, + load, + reset, + cancelChanges, + setSyncEnabled, + setCanonicalValue, + save, + saveAndWait, + } +} diff --git a/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-labels.ts b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-labels.ts new file mode 100644 index 0000000..d75f0b9 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/instances/game-settings-modal/use-labels.ts @@ -0,0 +1,138 @@ +import { useDebugLogger, useVIntl } from '@modrinth/ui' +import { listen, type UnlistenFn } from '@tauri-apps/api/event' +import { computed, type MaybeRefOrGetter, onScopeDispose, shallowRef, toValue, watch } from 'vue' + +import { + type EditableGameSetting, + type GameSettingLocaleLabel, + get_game_setting_locale_labels, +} from '@/helpers/game-options' + +export function useGameSettingLabels( + opened: MaybeRefOrGetter, + instanceId: MaybeRefOrGetter, + settings: MaybeRefOrGetter, +) { + const debug = useDebugLogger('GameSettingLabels') + const { locale } = useVIntl() + const labels = shallowRef>({}) + const optionIds = computed(() => + toValue(settings) + .filter((setting) => setting.kind === 'external') + .map((setting) => setting.option_id) + .sort(), + ) + let generation = 0 + let stopListening: UnlistenFn | undefined + let refreshSources = false + + debug('setup', { opened: toValue(opened), instanceId: toValue(instanceId), locale: locale.value }) + + async function refresh(reason: string) { + if (!toValue(opened) || !optionIds.value.length) { + debug('refresh skipped', { + reason, + opened: toValue(opened), + optionCount: optionIds.value.length, + }) + return + } + const request = ++generation + const reindex = refreshSources + const requestedIds = [...optionIds.value] + const started = performance.now() + refreshSources = false + debug('request started', { + request, + reason, + instanceId: toValue(instanceId), + locale: locale.value, + reindex, + optionIds: requestedIds, + }) + try { + const result = await get_game_setting_locale_labels( + toValue(instanceId), + locale.value, + requestedIds, + reindex, + ) + const applied = request === generation && toValue(opened) + debug('request completed', { + request, + generation, + applied, + opened: toValue(opened), + elapsedMs: Math.round(performance.now() - started), + returnedCount: Object.keys(result.settings).length, + missingOptionIds: requestedIds.filter((id) => !result.settings[id]), + labelSample: Object.entries(result.settings).slice(0, 8), + }) + if (applied) labels.value = result.settings + } catch (error) { + debug('request failed', { + request, + reason, + elapsedMs: Math.round(performance.now() - started), + error, + }) + } + } + + watch( + () => toValue(opened), + (active, _, onCleanup) => { + debug('modal state changed', { active, generation, optionCount: optionIds.value.length }) + let cancelled = false + onCleanup(() => { + cancelled = true + stopListening?.() + stopListening = undefined + generation++ + labels.value = {} + debug('modal cleanup: listener removed and labels cleared', { generation }) + }) + if (!active) return + refreshSources = true + debug('registering locale update listener') + void listen('game-option-locales-updated', () => { + debug('received game-option-locales-updated') + void refresh('locale update event') + }) + .then((unlisten) => { + debug('locale update listener registered', { cancelled }) + if (cancelled) unlisten() + else { + stopListening = unlisten + void refresh('listener registered') + } + }) + .catch((error) => { + debug('locale update listener failed', { cancelled, error }) + if (!cancelled) void refresh('listener failed') + }) + }, + { flush: 'sync' }, + ) + + watch([locale, () => toValue(instanceId), () => optionIds.value.join('\n')], () => { + generation++ + labels.value = {} + debug('locale, instance or option IDs changed: labels cleared', { + generation, + locale: locale.value, + instanceId: toValue(instanceId), + optionCount: optionIds.value.length, + }) + void refresh('request inputs changed') + }) + + onScopeDispose(() => { + generation++ + stopListening?.() + labels.value = {} + debug('scope disposed', { generation }) + }) + + return labels +} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 6bc7439..1a596a9 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -10351,5 +10351,1082 @@ }, "instance.settings.tabs.sync": { "message": "Synchronization" + }, + "app.settings.game-options.category.accessibility.description": { + "message": "Accessibility preferences" + }, + "app.settings.game-options.category.accessibility.label": { + "message": "Accessibility" + }, + "app.settings.game-options.category.chat.description": { + "message": "Chat visibility and appearance" + }, + "app.settings.game-options.category.chat.label": { + "message": "Chat" + }, + "app.settings.game-options.category.controls.description": { + "message": "Mouse, movement, and key bindings" + }, + "app.settings.game-options.category.controls.label": { + "message": "Controls" + }, + "app.settings.game-options.category.custom.description": { + "message": "Settings added by mods" + }, + "app.settings.game-options.category.custom.label": { + "message": "Custom settings" + }, + "app.settings.game-options.category.language.description": { + "message": "Game language" + }, + "app.settings.game-options.category.language.label": { + "message": "Language" + }, + "app.settings.game-options.category.music-and-sound.description": { + "message": "Volume and audio preferences" + }, + "app.settings.game-options.category.music-and-sound.label": { + "message": "Music and sound" + }, + "app.settings.game-options.category.online.description": { + "message": "Online and Realms preferences" + }, + "app.settings.game-options.category.online.label": { + "message": "Online" + }, + "app.settings.game-options.category.skin-customization.description": { + "message": "Skin layers and main hand" + }, + "app.settings.game-options.category.skin-customization.label": { + "message": "Skin customization" + }, + "app.settings.game-options.category.video.description": { + "message": "Camera and display settings" + }, + "app.settings.game-options.category.video.label": { + "message": "Video" + }, + "app.settings.game-options.choice.all": { + "message": "All" + }, + "app.settings.game-options.choice.balanced": { + "message": "Balanced" + }, + "app.settings.game-options.choice.by-player": { + "message": "By player" + }, + "app.settings.game-options.choice.chat": { + "message": "Chat" + }, + "app.settings.game-options.choice.commands-only": { + "message": "Commands only" + }, + "app.settings.game-options.choice.constant": { + "message": "Constant" + }, + "app.settings.game-options.choice.crosshair": { + "message": "Crosshair" + }, + "app.settings.game-options.choice.custom": { + "message": "Custom" + }, + "app.settings.game-options.choice.decreased": { + "message": "Decreased" + }, + "app.settings.game-options.choice.default": { + "message": "Default" + }, + "app.settings.game-options.choice.fabulous": { + "message": "Fabulous" + }, + "app.settings.game-options.choice.fancy": { + "message": "Fancy" + }, + "app.settings.game-options.choice.far": { + "message": "Far" + }, + "app.settings.game-options.choice.fast": { + "message": "Fast" + }, + "app.settings.game-options.choice.frequent": { + "message": "Frequent" + }, + "app.settings.game-options.choice.hidden": { + "message": "Hidden" + }, + "app.settings.game-options.choice.hotbar": { + "message": "Hotbar" + }, + "app.settings.game-options.choice.left": { + "message": "Left" + }, + "app.settings.game-options.choice.limited": { + "message": "Limited" + }, + "app.settings.game-options.choice.max-fps": { + "message": "Max FPS" + }, + "app.settings.game-options.choice.maximum": { + "message": "Maximum" + }, + "app.settings.game-options.choice.minimal": { + "message": "Minimal" + }, + "app.settings.game-options.choice.minimum": { + "message": "Minimum" + }, + "app.settings.game-options.choice.nearby": { + "message": "Nearby" + }, + "app.settings.game-options.choice.never": { + "message": "Never" + }, + "app.settings.game-options.choice.none": { + "message": "None" + }, + "app.settings.game-options.choice.normal": { + "message": "Normal" + }, + "app.settings.game-options.choice.off": { + "message": "Off" + }, + "app.settings.game-options.choice.on": { + "message": "On" + }, + "app.settings.game-options.choice.opengl": { + "message": "OpenGL" + }, + "app.settings.game-options.choice.pause": { + "message": "Pause menu" + }, + "app.settings.game-options.choice.pause-and-toast": { + "message": "Pause menu and toast" + }, + "app.settings.game-options.choice.power-saver": { + "message": "Power saver" + }, + "app.settings.game-options.choice.right": { + "message": "Right" + }, + "app.settings.game-options.choice.short": { + "message": "Short" + }, + "app.settings.game-options.choice.shown": { + "message": "Shown" + }, + "app.settings.game-options.choice.system": { + "message": "System" + }, + "app.settings.game-options.choice.tiny": { + "message": "Tiny" + }, + "app.settings.game-options.choice.vulkan": { + "message": "Vulkan" + }, + "app.settings.game-options.choice.when-minimized": { + "message": "When minimized" + }, + "app.settings.game-options.choice.while-afk": { + "message": "While AFK" + }, + "app.settings.game-options.compatibility.none": { + "message": "Some of your instances cannot use this setting" + }, + "app.settings.game-options.compatibility.reason.launcher-controlled": { + "message": "This setting is managed by Modrinth’s launch settings." + }, + "app.settings.game-options.custom-value.placeholder": { + "message": "Enter a value" + }, + "app.settings.game-options.keybind.assigned-status": { + "message": "{setting} is now bound to {binding}." + }, + "app.settings.game-options.keybind.cancelled-status": { + "message": "Key binding change cancelled." + }, + "app.settings.game-options.keybind.change": { + "message": "{setting}: {binding}. Activate to change the binding." + }, + "app.settings.game-options.keybind.choose": { + "message": "Choose a key" + }, + "app.settings.game-options.keybind.conflict-description": { + "message": "This is already used by {settings}." + }, + "app.settings.game-options.keybind.key.arrow-down": { + "message": "Down Arrow" + }, + "app.settings.game-options.keybind.key.arrow-left": { + "message": "Left Arrow" + }, + "app.settings.game-options.keybind.key.arrow-right": { + "message": "Right Arrow" + }, + "app.settings.game-options.keybind.key.arrow-up": { + "message": "Up Arrow" + }, + "app.settings.game-options.keybind.key.backspace": { + "message": "Backspace" + }, + "app.settings.game-options.keybind.key.caps-lock": { + "message": "Caps Lock" + }, + "app.settings.game-options.keybind.key.delete": { + "message": "Delete" + }, + "app.settings.game-options.keybind.key.end": { + "message": "End" + }, + "app.settings.game-options.keybind.key.enter": { + "message": "Enter" + }, + "app.settings.game-options.keybind.key.escape": { + "message": "Escape" + }, + "app.settings.game-options.keybind.key.home": { + "message": "Home" + }, + "app.settings.game-options.keybind.key.insert": { + "message": "Insert" + }, + "app.settings.game-options.keybind.key.keypad-key": { + "message": "Numpad {key}" + }, + "app.settings.game-options.keybind.key.left-alt": { + "message": "Left Alt" + }, + "app.settings.game-options.keybind.key.left-command": { + "message": "Left Command" + }, + "app.settings.game-options.keybind.key.left-control": { + "message": "Left Ctrl" + }, + "app.settings.game-options.keybind.key.left-shift": { + "message": "Left Shift" + }, + "app.settings.game-options.keybind.key.left-super": { + "message": "Left Super" + }, + "app.settings.game-options.keybind.key.menu": { + "message": "Menu" + }, + "app.settings.game-options.keybind.key.num-lock": { + "message": "Num Lock" + }, + "app.settings.game-options.keybind.key.page-down": { + "message": "Page Down" + }, + "app.settings.game-options.keybind.key.page-up": { + "message": "Page Up" + }, + "app.settings.game-options.keybind.key.pause": { + "message": "Pause" + }, + "app.settings.game-options.keybind.key.print-screen": { + "message": "Print Screen" + }, + "app.settings.game-options.keybind.key.right-alt": { + "message": "Right Alt" + }, + "app.settings.game-options.keybind.key.right-command": { + "message": "Right Command" + }, + "app.settings.game-options.keybind.key.right-control": { + "message": "Right Ctrl" + }, + "app.settings.game-options.keybind.key.right-shift": { + "message": "Right Shift" + }, + "app.settings.game-options.keybind.key.right-super": { + "message": "Right Super" + }, + "app.settings.game-options.keybind.key.scroll-lock": { + "message": "Scroll Lock" + }, + "app.settings.game-options.keybind.key.space": { + "message": "Space" + }, + "app.settings.game-options.keybind.key.tab": { + "message": "Tab" + }, + "app.settings.game-options.keybind.key.unbound": { + "message": "Unbound" + }, + "app.settings.game-options.keybind.key.unknown": { + "message": "Unknown key" + }, + "app.settings.game-options.keybind.listen": { + "message": "Press a key…" + }, + "app.settings.game-options.keybind.listening-status": { + "message": "Listening for a key or mouse button. Press Escape to clear this binding." + }, + "app.settings.game-options.keybind.mixed": { + "message": "Different across instances" + }, + "app.settings.game-options.keybind.modifier.alt": { + "message": "Alt" + }, + "app.settings.game-options.keybind.modifier.command": { + "message": "Command" + }, + "app.settings.game-options.keybind.modifier.control": { + "message": "Ctrl" + }, + "app.settings.game-options.keybind.modifier.shift": { + "message": "Shift" + }, + "app.settings.game-options.keybind.modifier.super": { + "message": "Super" + }, + "app.settings.game-options.keybind.mouse.button": { + "message": "Mouse {button}" + }, + "app.settings.game-options.keybind.mouse.left": { + "message": "Left Mouse" + }, + "app.settings.game-options.keybind.mouse.middle": { + "message": "Middle Mouse" + }, + "app.settings.game-options.keybind.mouse.right": { + "message": "Right Mouse" + }, + "app.settings.game-options.keybind.recording-label": { + "message": "{setting}: listening for a key or mouse button. Press Escape to clear this binding." + }, + "app.settings.game-options.keybind.scancode": { + "message": "Scancode {code}" + }, + "app.settings.game-options.keybind.unsupported-status": { + "message": "That input cannot be used by Minecraft. Press another key." + }, + "app.settings.game-options.setting.advanced-opengl.label": { + "message": "Advanced OpenGL" + }, + "app.settings.game-options.setting.allow-cursor-changes.label": { + "message": "Allow cursor changes" + }, + "app.settings.game-options.setting.allow-server-listing.description": { + "message": "Allow the player's name to appear in server listings." + }, + "app.settings.game-options.setting.allow-server-listing.label": { + "message": "Server listings" + }, + "app.settings.game-options.setting.alternate-blocks.label": { + "message": "Alternate blocks" + }, + "app.settings.game-options.setting.ambient-occlusion.label": { + "message": "Smooth lighting" + }, + "app.settings.game-options.setting.ambient-volume.label": { + "message": "Ambient/Environment" + }, + "app.settings.game-options.setting.anaglyph-3d.label": { + "message": "3D anaglyph" + }, + "app.settings.game-options.setting.anisotropic-filtering.label": { + "message": "Anisotropic filtering" + }, + "app.settings.game-options.setting.anisotropy.label": { + "message": "Anisotropy" + }, + "app.settings.game-options.setting.attack-indicator.label": { + "message": "Attack indicator" + }, + "app.settings.game-options.setting.auto-jump.description": { + "message": "Automatically jump up one-block-high obstacles." + }, + "app.settings.game-options.setting.auto-jump.label": { + "message": "Auto-jump" + }, + "app.settings.game-options.setting.auto-suggestions.label": { + "message": "Command suggestions" + }, + "app.settings.game-options.setting.autosave-indicator.label": { + "message": "Autosave indicator" + }, + "app.settings.game-options.setting.biome-blend-radius.description": { + "message": "The distance over which biome colors transition." + }, + "app.settings.game-options.setting.biome-blend-radius.label": { + "message": "Biome blend" + }, + "app.settings.game-options.setting.blocks-volume.label": { + "message": "Blocks" + }, + "app.settings.game-options.setting.brightness.label": { + "message": "Brightness" + }, + "app.settings.game-options.setting.cape.description": { + "message": "Show the player's cape, including its elytra texture." + }, + "app.settings.game-options.setting.cape.label": { + "message": "Cape" + }, + "app.settings.game-options.setting.chat-background-only.label": { + "message": "Chat background only" + }, + "app.settings.game-options.setting.chat-colors.label": { + "message": "Chat colors" + }, + "app.settings.game-options.setting.chat-delay.label": { + "message": "Chat delay" + }, + "app.settings.game-options.setting.chat-line-spacing.label": { + "message": "Chat line spacing" + }, + "app.settings.game-options.setting.chat-links-prompt.description": { + "message": "Ask before opening links from chat." + }, + "app.settings.game-options.setting.chat-links-prompt.label": { + "message": "Prompt on links" + }, + "app.settings.game-options.setting.chat-links.description": { + "message": "Allow web links in chat to be opened." + }, + "app.settings.game-options.setting.chat-links.label": { + "message": "Web links" + }, + "app.settings.game-options.setting.chat-opacity.description": { + "message": "The opacity of chat text." + }, + "app.settings.game-options.setting.chat-opacity.label": { + "message": "Chat opacity" + }, + "app.settings.game-options.setting.chat-preview.label": { + "message": "Chat preview" + }, + "app.settings.game-options.setting.chat-scale.label": { + "message": "Chat scale" + }, + "app.settings.game-options.setting.chat-visibility.label": { + "message": "Chat visibility" + }, + "app.settings.game-options.setting.chat-width.label": { + "message": "Chat width" + }, + "app.settings.game-options.setting.chunk-fade-time.label": { + "message": "Chunk fade time" + }, + "app.settings.game-options.setting.cloud-range.label": { + "message": "Cloud distance" + }, + "app.settings.game-options.setting.clouds.label": { + "message": "Clouds" + }, + "app.settings.game-options.setting.ctrl-click-right-click.label": { + "message": "Control-click as right-click" + }, + "app.settings.game-options.setting.cutout-leaves.label": { + "message": "Cutout leaves" + }, + "app.settings.game-options.setting.damage-tilt.label": { + "message": "Damage tilt" + }, + "app.settings.game-options.setting.dark-splash.description": { + "message": "Change the Mojang Studios loading screen from red to black." + }, + "app.settings.game-options.setting.dark-splash.label": { + "message": "Monochrome logo" + }, + "app.settings.game-options.setting.darkness-pulsing.label": { + "message": "Darkness pulsing" + }, + "app.settings.game-options.setting.debug-gui-scale.label": { + "message": "Debug GUI scale" + }, + "app.settings.game-options.setting.directional-audio.label": { + "message": "Directional audio" + }, + "app.settings.game-options.setting.discrete-mouse-scroll.description": { + "message": "Treat each mouse-wheel input as a single scroll step." + }, + "app.settings.game-options.setting.discrete-mouse-scroll.label": { + "message": "Discrete scrolling" + }, + "app.settings.game-options.setting.entity-distance.label": { + "message": "Entity distance" + }, + "app.settings.game-options.setting.entity-shadows.label": { + "message": "Entity shadows" + }, + "app.settings.game-options.setting.exclusive-fullscreen.label": { + "message": "Exclusive fullscreen" + }, + "app.settings.game-options.setting.extra-telemetry.label": { + "message": "Optional telemetry" + }, + "app.settings.game-options.setting.focused-chat-height.label": { + "message": "Focused chat height" + }, + "app.settings.game-options.setting.force-unicode-font.label": { + "message": "Force Unicode font" + }, + "app.settings.game-options.setting.fov-effects.label": { + "message": "FOV effects" + }, + "app.settings.game-options.setting.fov.label": { + "message": "Field of view" + }, + "app.settings.game-options.setting.fullscreen.label": { + "message": "Fullscreen" + }, + "app.settings.game-options.setting.glint-speed.label": { + "message": "Glint speed" + }, + "app.settings.game-options.setting.glint-strength.label": { + "message": "Glint strength" + }, + "app.settings.game-options.setting.graphics-backend.label": { + "message": "Graphics backend" + }, + "app.settings.game-options.setting.graphics.description": { + "message": "Controls visual quality and performance." + }, + "app.settings.game-options.setting.graphics.label": { + "message": "Graphics" + }, + "app.settings.game-options.setting.gui-scale.description": { + "message": "The size of the game interface and HUD." + }, + "app.settings.game-options.setting.gui-scale.label": { + "message": "GUI scale" + }, + "app.settings.game-options.setting.hat.description": { + "message": "Show the hat skin layer." + }, + "app.settings.game-options.setting.hat.label": { + "message": "Hat" + }, + "app.settings.game-options.setting.held-item-tooltips.label": { + "message": "Held item tooltips" + }, + "app.settings.game-options.setting.hide-lightning-flashes.label": { + "message": "Hide lightning flashes" + }, + "app.settings.game-options.setting.hide-matched-names.label": { + "message": "Hide matched names" + }, + "app.settings.game-options.setting.hide-server-address.label": { + "message": "Hide server address" + }, + "app.settings.game-options.setting.hide-splash-texts.label": { + "message": "Hide splash texts" + }, + "app.settings.game-options.setting.high-contrast-outline.label": { + "message": "High contrast block outline" + }, + "app.settings.game-options.setting.high-contrast.description": { + "message": "Enhance the contrast of interface elements." + }, + "app.settings.game-options.setting.high-contrast.label": { + "message": "High contrast" + }, + "app.settings.game-options.setting.hostile-volume.label": { + "message": "Hostile creatures" + }, + "app.settings.game-options.setting.improved-transparency.label": { + "message": "Improved transparency" + }, + "app.settings.game-options.setting.in-game-notifications.label": { + "message": "In-game notifications" + }, + "app.settings.game-options.setting.inactivity-framerate-limit.label": { + "message": "Reduced framerate" + }, + "app.settings.game-options.setting.invert-horizontal-mouse.label": { + "message": "Invert horizontal mouse" + }, + "app.settings.game-options.setting.invert-mouse.description": { + "message": "Invert vertical mouse movement." + }, + "app.settings.game-options.setting.invert-mouse.label": { + "message": "Invert mouse" + }, + "app.settings.game-options.setting.jacket.description": { + "message": "Show the jacket skin layer." + }, + "app.settings.game-options.setting.jacket.label": { + "message": "Jacket" + }, + "app.settings.game-options.setting.japanese-glyph-variants.label": { + "message": "Japanese glyph variants" + }, + "app.settings.game-options.setting.key-advancements.label": { + "message": "Advancements" + }, + "app.settings.game-options.setting.key-attack.label": { + "message": "Attack" + }, + "app.settings.game-options.setting.key-back.label": { + "message": "Move backward" + }, + "app.settings.game-options.setting.key-chat.label": { + "message": "Open chat" + }, + "app.settings.game-options.setting.key-command.label": { + "message": "Command" + }, + "app.settings.game-options.setting.key-debug-advanced-tooltips.label": { + "message": "Show advanced tooltips" + }, + "app.settings.game-options.setting.key-debug-chunk-borders.label": { + "message": "Show chunk borders" + }, + "app.settings.game-options.setting.key-debug-clear-chat.label": { + "message": "Clear chat" + }, + "app.settings.game-options.setting.key-debug-copy-location.label": { + "message": "Copy location" + }, + "app.settings.game-options.setting.key-debug-copy-recreate-command.label": { + "message": "Copy recreate command" + }, + "app.settings.game-options.setting.key-debug-crash.label": { + "message": "Trigger debug crash" + }, + "app.settings.game-options.setting.key-debug-dump-dynamic-textures.label": { + "message": "Dump dynamic textures" + }, + "app.settings.game-options.setting.key-debug-dump-version.label": { + "message": "Dump version" + }, + "app.settings.game-options.setting.key-debug-focus-pause.label": { + "message": "Pause on lost focus" + }, + "app.settings.game-options.setting.key-debug-fps-charts.label": { + "message": "FPS charts" + }, + "app.settings.game-options.setting.key-debug-hitboxes.label": { + "message": "Show hitboxes" + }, + "app.settings.game-options.setting.key-debug-improved-transparency.label": { + "message": "Improved transparency debug view" + }, + "app.settings.game-options.setting.key-debug-lightmap-texture.label": { + "message": "Lightmap texture" + }, + "app.settings.game-options.setting.key-debug-modifier.label": { + "message": "Debug modifier" + }, + "app.settings.game-options.setting.key-debug-network-charts.label": { + "message": "Network charts" + }, + "app.settings.game-options.setting.key-debug-options.label": { + "message": "Debug options" + }, + "app.settings.game-options.setting.key-debug-overlay.label": { + "message": "Debug overlay" + }, + "app.settings.game-options.setting.key-debug-profiling-chart.label": { + "message": "Profiling chart" + }, + "app.settings.game-options.setting.key-debug-profiling.label": { + "message": "Start profiling" + }, + "app.settings.game-options.setting.key-debug-reload-chunks.label": { + "message": "Reload chunks" + }, + "app.settings.game-options.setting.key-debug-reload-resource-packs.label": { + "message": "Reload resource packs" + }, + "app.settings.game-options.setting.key-debug-spectate.label": { + "message": "Spectate entity" + }, + "app.settings.game-options.setting.key-debug-switch-game-mode.label": { + "message": "Switch game mode" + }, + "app.settings.game-options.setting.key-drop.label": { + "message": "Drop item" + }, + "app.settings.game-options.setting.key-forward.label": { + "message": "Move forward" + }, + "app.settings.game-options.setting.key-friends.label": { + "message": "Friends" + }, + "app.settings.game-options.setting.key-fullscreen.label": { + "message": "Toggle fullscreen" + }, + "app.settings.game-options.setting.key-hotbar-1.label": { + "message": "Hotbar 1" + }, + "app.settings.game-options.setting.key-hotbar-2.label": { + "message": "Hotbar 2" + }, + "app.settings.game-options.setting.key-hotbar-3.label": { + "message": "Hotbar 3" + }, + "app.settings.game-options.setting.key-hotbar-4.label": { + "message": "Hotbar 4" + }, + "app.settings.game-options.setting.key-hotbar-5.label": { + "message": "Hotbar 5" + }, + "app.settings.game-options.setting.key-hotbar-6.label": { + "message": "Hotbar 6" + }, + "app.settings.game-options.setting.key-hotbar-7.label": { + "message": "Hotbar 7" + }, + "app.settings.game-options.setting.key-hotbar-8.label": { + "message": "Hotbar 8" + }, + "app.settings.game-options.setting.key-hotbar-9.label": { + "message": "Hotbar 9" + }, + "app.settings.game-options.setting.key-inventory.label": { + "message": "Inventory" + }, + "app.settings.game-options.setting.key-jump.label": { + "message": "Jump" + }, + "app.settings.game-options.setting.key-left.label": { + "message": "Strafe left" + }, + "app.settings.game-options.setting.key-load-toolbar.label": { + "message": "Load toolbar" + }, + "app.settings.game-options.setting.key-perspective.label": { + "message": "Change perspective" + }, + "app.settings.game-options.setting.key-pick-item.label": { + "message": "Pick block" + }, + "app.settings.game-options.setting.key-player-list.label": { + "message": "Player list" + }, + "app.settings.game-options.setting.key-quick-actions.label": { + "message": "Quick actions" + }, + "app.settings.game-options.setting.key-right.label": { + "message": "Strafe right" + }, + "app.settings.game-options.setting.key-save-toolbar.label": { + "message": "Save toolbar" + }, + "app.settings.game-options.setting.key-screenshot.label": { + "message": "Screenshot" + }, + "app.settings.game-options.setting.key-smooth-camera.label": { + "message": "Toggle cinematic camera" + }, + "app.settings.game-options.setting.key-sneak.label": { + "message": "Sneak" + }, + "app.settings.game-options.setting.key-social-interactions.label": { + "message": "Social interactions" + }, + "app.settings.game-options.setting.key-spectator-hotbar.label": { + "message": "Spectator hotbar" + }, + "app.settings.game-options.setting.key-spectator-outlines.label": { + "message": "Highlight spectators" + }, + "app.settings.game-options.setting.key-sprint.label": { + "message": "Sprint" + }, + "app.settings.game-options.setting.key-swap-offhand.label": { + "message": "Swap offhand" + }, + "app.settings.game-options.setting.key-toggle-gui.label": { + "message": "Toggle HUD" + }, + "app.settings.game-options.setting.key-toggle-spectator-shader.label": { + "message": "Toggle spectator shader" + }, + "app.settings.game-options.setting.key-use.label": { + "message": "Use item" + }, + "app.settings.game-options.setting.language.label": { + "message": "Language" + }, + "app.settings.game-options.setting.left-pants-leg.label": { + "message": "Left pants leg" + }, + "app.settings.game-options.setting.left-sleeve.label": { + "message": "Left sleeve" + }, + "app.settings.game-options.setting.legacy-framerate-limit.label": { + "message": "Framerate limit" + }, + "app.settings.game-options.setting.legacy-view-distance.label": { + "message": "View distance" + }, + "app.settings.game-options.setting.mac-fullscreen-menu.label": { + "message": "Show macOS menu in fullscreen" + }, + "app.settings.game-options.setting.main-hand.description": { + "message": "Choose whether the main hand is left or right." + }, + "app.settings.game-options.setting.main-hand.label": { + "message": "Main hand" + }, + "app.settings.game-options.setting.master-volume.label": { + "message": "Master volume" + }, + "app.settings.game-options.setting.max-framerate.label": { + "message": "Maximum framerate" + }, + "app.settings.game-options.setting.menu-background-blur.label": { + "message": "Menu background blur" + }, + "app.settings.game-options.setting.mipmap-levels.description": { + "message": "Texture smoothing at a distance." + }, + "app.settings.game-options.setting.mipmap-levels.label": { + "message": "Mipmap levels" + }, + "app.settings.game-options.setting.mouse-wheel-sensitivity.label": { + "message": "Mouse wheel sensitivity" + }, + "app.settings.game-options.setting.music-frequency.label": { + "message": "Music frequency" + }, + "app.settings.game-options.setting.music-toast.description": { + "message": "Choose whether music titles appear in the pause menu and as toasts." + }, + "app.settings.game-options.setting.music-toast.label": { + "message": "Music notification" + }, + "app.settings.game-options.setting.music-volume.label": { + "message": "Music" + }, + "app.settings.game-options.setting.narrator-hotkey.label": { + "message": "Narrator hotkey" + }, + "app.settings.game-options.setting.narrator.description": { + "message": "Choose what the narrator reads." + }, + "app.settings.game-options.setting.narrator.label": { + "message": "Narrator" + }, + "app.settings.game-options.setting.neutral-volume.label": { + "message": "Friendly creatures" + }, + "app.settings.game-options.setting.notification-time.description": { + "message": "How long toast notifications remain visible." + }, + "app.settings.game-options.setting.notification-time.label": { + "message": "Notification time" + }, + "app.settings.game-options.setting.operator-items-tab.label": { + "message": "Operator items tab" + }, + "app.settings.game-options.setting.panorama-speed.label": { + "message": "Panorama speed" + }, + "app.settings.game-options.setting.particles.label": { + "message": "Particles" + }, + "app.settings.game-options.setting.players-volume.label": { + "message": "Players" + }, + "app.settings.game-options.setting.prioritize-chunk-updates.label": { + "message": "Prioritize chunk updates" + }, + "app.settings.game-options.setting.quit-shortcuts.label": { + "message": "Quit shortcuts" + }, + "app.settings.game-options.setting.raw-mouse-input.label": { + "message": "Raw mouse input" + }, + "app.settings.game-options.setting.realms-notifications.label": { + "message": "Realms notifications" + }, + "app.settings.game-options.setting.record-volume.label": { + "message": "Jukebox/Note Blocks" + }, + "app.settings.game-options.setting.reduced-debug-info.label": { + "message": "Reduced debug information" + }, + "app.settings.game-options.setting.render-distance.label": { + "message": "Render distance" + }, + "app.settings.game-options.setting.right-pants-leg.label": { + "message": "Right pants leg" + }, + "app.settings.game-options.setting.right-sleeve.label": { + "message": "Right sleeve" + }, + "app.settings.game-options.setting.rotate-with-minecart.label": { + "message": "Rotate with minecart" + }, + "app.settings.game-options.setting.save-chat-drafts.label": { + "message": "Save chat drafts" + }, + "app.settings.game-options.setting.screen-effects.label": { + "message": "Screen effects" + }, + "app.settings.game-options.setting.secure-chat-only.label": { + "message": "Only show secure chat" + }, + "app.settings.game-options.setting.sensitivity.label": { + "message": "Mouse sensitivity" + }, + "app.settings.game-options.setting.server-textures.label": { + "message": "Server textures" + }, + "app.settings.game-options.setting.share-presence.label": { + "message": "Share presence" + }, + "app.settings.game-options.setting.simulation-distance.description": { + "message": "How far away entities update and blocks and fluids tick." + }, + "app.settings.game-options.setting.simulation-distance.label": { + "message": "Simulation distance" + }, + "app.settings.game-options.setting.snooper.label": { + "message": "Snooper" + }, + "app.settings.game-options.setting.sprint-window.label": { + "message": "Sprint window" + }, + "app.settings.game-options.setting.subtitles.description": { + "message": "Show captions for sounds played in the game." + }, + "app.settings.game-options.setting.subtitles.label": { + "message": "Subtitles" + }, + "app.settings.game-options.setting.text-background-opacity.label": { + "message": "Text background opacity" + }, + "app.settings.game-options.setting.texture-filtering.label": { + "message": "Texture filtering" + }, + "app.settings.game-options.setting.toggle-attack.label": { + "message": "Toggle attack" + }, + "app.settings.game-options.setting.toggle-crouch.description": { + "message": "Press once to remain crouched." + }, + "app.settings.game-options.setting.toggle-crouch.label": { + "message": "Toggle crouch" + }, + "app.settings.game-options.setting.toggle-sprint.description": { + "message": "Press once to remain sprinting." + }, + "app.settings.game-options.setting.toggle-sprint.label": { + "message": "Toggle sprint" + }, + "app.settings.game-options.setting.toggle-use.label": { + "message": "Toggle use" + }, + "app.settings.game-options.setting.touchscreen.label": { + "message": "Touchscreen mode" + }, + "app.settings.game-options.setting.ui-volume.label": { + "message": "UI" + }, + "app.settings.game-options.setting.unfocused-chat-height.label": { + "message": "Unfocused chat height" + }, + "app.settings.game-options.setting.use-vbo.label": { + "message": "Use VBOs" + }, + "app.settings.game-options.setting.view-bobbing.description": { + "message": "Add a bobbing motion to the camera while walking." + }, + "app.settings.game-options.setting.view-bobbing.label": { + "message": "View bobbing" + }, + "app.settings.game-options.setting.vignette.label": { + "message": "Vignette" + }, + "app.settings.game-options.setting.voice-volume.label": { + "message": "Voice and speech" + }, + "app.settings.game-options.setting.vsync.description": { + "message": "Limit the frame rate to the display refresh rate to prevent screen tearing." + }, + "app.settings.game-options.setting.vsync.label": { + "message": "VSync" + }, + "app.settings.game-options.setting.weather-radius.label": { + "message": "Weather radius" + }, + "app.settings.game-options.setting.weather-volume.label": { + "message": "Weather" + }, + "app.settings.game-options.validation.changed-since-opened": { + "message": "This setting changed elsewhere. Check it and try again." + }, + "app.settings.game-options.validation.invalid-value": { + "message": "Choose a valid value." + }, + "app.settings.game-options.validation.missing-value": { + "message": "Choose a value first." + }, + "app.settings.game-options.validation.no-compatible-instances": { + "message": "Some of your instances cannot use this setting" + }, + "app.settings.synced-options.game-settings.conflict-text": { + "message": "We loaded the latest settings. Check your changes, then save again." + }, + "app.settings.synced-options.game-settings.conflict-title": { + "message": "These settings changed elsewhere" + }, + "app.settings.synced-options.game-settings.custom-empty": { + "message": "Settings added by mods will appear here after Modrinth finds them in one of your instances." + }, + "app.settings.synced-options.game-settings.disable-all": { + "message": "Unsync all" + }, + "app.settings.synced-options.game-settings.empty": { + "message": "No settings match your search or filter." + }, + "app.settings.synced-options.game-settings.enable-all": { + "message": "Sync all" + }, + "app.settings.synced-options.game-settings.load-failed": { + "message": "We couldn’t load your game settings." + }, + "app.settings.synced-options.game-settings.loading": { + "message": "Loading settings..." + }, + "app.settings.synced-options.game-settings.not-being-synced": { + "message": "Not being synced" + }, + "app.settings.synced-options.game-settings.retry": { + "message": "Retry" + }, + "app.settings.synced-options.game-settings.save": { + "message": "Save settings" + }, + "app.settings.synced-options.game-settings.search": { + "message": "Search settings..." + }, + "app.settings.synced-options.game-settings.sync-description": { + "message": "Synced options are applied to every instance and override the values included with a modpack." + }, + "app.settings.synced-options.game-settings.sync-setting": { + "message": "Sync {setting} across instances" + }, + "app.settings.synced-options.game-settings.sync-title": { + "message": "Sync game settings" + }, + "app.settings.synced-options.game-settings.synced-across-instances": { + "message": "Synced across instances" + }, + "app.settings.synced-options.game-settings.title": { + "message": "Game settings" + }, + "app.settings.synced-options.game-settings.unsaved-changes": { + "message": "You have unsaved changes." + }, + "app.settings.synced-options.game-settings.unsync-setting": { + "message": "Stop syncing {setting} across instances" + }, + "app.settings.synced-options.game-settings.value-mixed": { + "message": "Different across instances" + }, + "app.settings.synced-options.game-settings.value-off": { + "message": "Off" + }, + "app.settings.synced-options.game-settings.value-on": { + "message": "On" + }, + "app.settings.synced-options.game-settings.value-unset": { + "message": "Choose a value" } } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index c88a4d8..3fd5974 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -10991,5 +10991,364 @@ "app.sync-instances-update.choose-source.data-packs": { "message": "选择要从哪个实例复制数据包。这些包仅用于初始化同步,之后可从任意实例进行编辑。" }, "app.sync-instances-update.choose-source.resource-packs": { "message": "选择要从哪个实例复制资源包。这些包仅用于初始化同步,之后可从任意实例进行编辑。" }, "instance.settings.tabs.backups": { "message": "备份" }, - "instance.settings.tabs.sync": { "message": "同步" } + "instance.settings.tabs.sync": { "message": "同步" }, + "app.settings.game-options.category.accessibility.description": { "message": "无障碍设置" }, + "app.settings.game-options.category.accessibility.label": { "message": "无障碍" }, + "app.settings.game-options.category.chat.description": { "message": "聊天显示与外观" }, + "app.settings.game-options.category.chat.label": { "message": "聊天" }, + "app.settings.game-options.category.controls.description": { "message": "鼠标、移动与按键绑定" }, + "app.settings.game-options.category.controls.label": { "message": "控制" }, + "app.settings.game-options.category.custom.description": { "message": "由模组添加的设置" }, + "app.settings.game-options.category.custom.label": { "message": "自定义设置" }, + "app.settings.game-options.category.language.description": { "message": "游戏语言" }, + "app.settings.game-options.category.language.label": { "message": "语言" }, + "app.settings.game-options.category.music-and-sound.description": { "message": "音量与音频偏好" }, + "app.settings.game-options.category.music-and-sound.label": { "message": "音乐和声音" }, + "app.settings.game-options.category.online.description": { "message": "在线与 Realms 偏好" }, + "app.settings.game-options.category.online.label": { "message": "在线" }, + "app.settings.game-options.category.skin-customization.description": { "message": "皮肤层与主手" }, + "app.settings.game-options.category.skin-customization.label": { "message": "皮肤自定义" }, + "app.settings.game-options.category.video.description": { "message": "镜头与显示设置" }, + "app.settings.game-options.category.video.label": { "message": "视频" }, + "app.settings.game-options.choice.all": { "message": "全部" }, + "app.settings.game-options.choice.balanced": { "message": "平衡" }, + "app.settings.game-options.choice.by-player": { "message": "按玩家" }, + "app.settings.game-options.choice.chat": { "message": "聊天" }, + "app.settings.game-options.choice.commands-only": { "message": "仅命令" }, + "app.settings.game-options.choice.constant": { "message": "持续" }, + "app.settings.game-options.choice.crosshair": { "message": "准星" }, + "app.settings.game-options.choice.custom": { "message": "自定义" }, + "app.settings.game-options.choice.decreased": { "message": "降低" }, + "app.settings.game-options.choice.default": { "message": "默认" }, + "app.settings.game-options.choice.fabulous": { "message": "极佳" }, + "app.settings.game-options.choice.fancy": { "message": "精美" }, + "app.settings.game-options.choice.far": { "message": "远" }, + "app.settings.game-options.choice.fast": { "message": "快速" }, + "app.settings.game-options.choice.frequent": { "message": "频繁" }, + "app.settings.game-options.choice.hidden": { "message": "隐藏" }, + "app.settings.game-options.choice.hotbar": { "message": "快捷栏" }, + "app.settings.game-options.choice.left": { "message": "左" }, + "app.settings.game-options.choice.limited": { "message": "有限" }, + "app.settings.game-options.choice.max-fps": { "message": "最高 FPS" }, + "app.settings.game-options.choice.maximum": { "message": "最大" }, + "app.settings.game-options.choice.minimal": { "message": "最少" }, + "app.settings.game-options.choice.minimum": { "message": "最小" }, + "app.settings.game-options.choice.nearby": { "message": "附近" }, + "app.settings.game-options.choice.never": { "message": "从不" }, + "app.settings.game-options.choice.none": { "message": "无" }, + "app.settings.game-options.choice.normal": { "message": "正常" }, + "app.settings.game-options.choice.off": { "message": "关闭" }, + "app.settings.game-options.choice.on": { "message": "开启" }, + "app.settings.game-options.choice.opengl": { "message": "OpenGL 渲染器" }, + "app.settings.game-options.choice.pause": { "message": "暂停菜单" }, + "app.settings.game-options.choice.pause-and-toast": { "message": "暂停菜单和提示" }, + "app.settings.game-options.choice.power-saver": { "message": "省电" }, + "app.settings.game-options.choice.right": { "message": "右" }, + "app.settings.game-options.choice.short": { "message": "短" }, + "app.settings.game-options.choice.shown": { "message": "显示" }, + "app.settings.game-options.choice.system": { "message": "系统" }, + "app.settings.game-options.choice.tiny": { "message": "极小" }, + "app.settings.game-options.choice.vulkan": { "message": "Vulkan 渲染器" }, + "app.settings.game-options.choice.when-minimized": { "message": "最小化时" }, + "app.settings.game-options.choice.while-afk": { "message": "挂机时" }, + "app.settings.game-options.compatibility.none": { "message": "部分实例无法使用此设置" }, + "app.settings.game-options.compatibility.reason.launcher-controlled": { "message": "此设置由启动器设置管理" }, + "app.settings.game-options.custom-value.placeholder": { "message": "输入值" }, + "app.settings.game-options.keybind.assigned-status": { "message": "已将 {setting} 绑定为 {binding}。" }, + "app.settings.game-options.keybind.cancelled-status": { "message": "已取消按键绑定更改。" }, + "app.settings.game-options.keybind.change": { "message": "{setting}:{binding}。激活以更改绑定。" }, + "app.settings.game-options.keybind.choose": { "message": "选择按键" }, + "app.settings.game-options.keybind.conflict-description": { "message": "{settings} 已使用此按键。" }, + "app.settings.game-options.keybind.key.arrow-down": { "message": "向下箭头" }, + "app.settings.game-options.keybind.key.arrow-left": { "message": "向左箭头" }, + "app.settings.game-options.keybind.key.arrow-right": { "message": "向右箭头" }, + "app.settings.game-options.keybind.key.arrow-up": { "message": "向上箭头" }, + "app.settings.game-options.keybind.key.backspace": { "message": "退格" }, + "app.settings.game-options.keybind.key.caps-lock": { "message": "大写锁定" }, + "app.settings.game-options.keybind.key.delete": { "message": "删除" }, + "app.settings.game-options.keybind.key.end": { "message": "End 键" }, + "app.settings.game-options.keybind.key.enter": { "message": "回车" }, + "app.settings.game-options.keybind.key.escape": { "message": "Esc 键" }, + "app.settings.game-options.keybind.key.home": { "message": "Home 键" }, + "app.settings.game-options.keybind.key.insert": { "message": "Insert 键" }, + "app.settings.game-options.keybind.key.keypad-key": { "message": "数字键盘 {key}" }, + "app.settings.game-options.keybind.key.left-alt": { "message": "左 Alt" }, + "app.settings.game-options.keybind.key.left-command": { "message": "左 Command" }, + "app.settings.game-options.keybind.key.left-control": { "message": "左 Ctrl" }, + "app.settings.game-options.keybind.key.left-shift": { "message": "左 Shift" }, + "app.settings.game-options.keybind.key.left-super": { "message": "左 Super" }, + "app.settings.game-options.keybind.key.menu": { "message": "菜单" }, + "app.settings.game-options.keybind.key.num-lock": { "message": "数字锁定" }, + "app.settings.game-options.keybind.key.page-down": { "message": "Page Down 键" }, + "app.settings.game-options.keybind.key.page-up": { "message": "Page Up 键" }, + "app.settings.game-options.keybind.key.pause": { "message": "暂停" }, + "app.settings.game-options.keybind.key.print-screen": { "message": "截图" }, + "app.settings.game-options.keybind.key.right-alt": { "message": "右 Alt" }, + "app.settings.game-options.keybind.key.right-command": { "message": "右 Command" }, + "app.settings.game-options.keybind.key.right-control": { "message": "右 Ctrl" }, + "app.settings.game-options.keybind.key.right-shift": { "message": "右 Shift" }, + "app.settings.game-options.keybind.key.right-super": { "message": "右 Super" }, + "app.settings.game-options.keybind.key.scroll-lock": { "message": "滚动锁定" }, + "app.settings.game-options.keybind.key.space": { "message": "空格" }, + "app.settings.game-options.keybind.key.tab": { "message": "Tab 键" }, + "app.settings.game-options.keybind.key.unbound": { "message": "未绑定" }, + "app.settings.game-options.keybind.key.unknown": { "message": "未知按键" }, + "app.settings.game-options.keybind.listen": { "message": "请按下一个按键……" }, + "app.settings.game-options.keybind.listening-status": { "message": "正在监听按键或鼠标按钮。按 Esc 清除绑定。" }, + "app.settings.game-options.keybind.mixed": { "message": "各实例不同" }, + "app.settings.game-options.keybind.modifier.alt": { "message": "Alt 键" }, + "app.settings.game-options.keybind.modifier.command": { "message": "Command 键" }, + "app.settings.game-options.keybind.modifier.control": { "message": "Ctrl 键" }, + "app.settings.game-options.keybind.modifier.shift": { "message": "Shift 键" }, + "app.settings.game-options.keybind.modifier.super": { "message": "Super 键" }, + "app.settings.game-options.keybind.mouse.button": { "message": "鼠标 {button}" }, + "app.settings.game-options.keybind.mouse.left": { "message": "鼠标左键" }, + "app.settings.game-options.keybind.mouse.middle": { "message": "鼠标中键" }, + "app.settings.game-options.keybind.mouse.right": { "message": "鼠标右键" }, + "app.settings.game-options.keybind.recording-label": { "message": "{setting}:正在监听按键或鼠标按钮。按 Esc 清除绑定。" }, + "app.settings.game-options.keybind.scancode": { "message": "扫描码 {code}" }, + "app.settings.game-options.keybind.unsupported-status": { "message": "Minecraft 无法使用此输入,请尝试其他按键。" }, + "app.settings.game-options.setting.advanced-opengl.label": { "message": "高级 OpenGL" }, + "app.settings.game-options.setting.allow-cursor-changes.label": { "message": "允许光标变化" }, + "app.settings.game-options.setting.allow-server-listing.description": { "message": "允许玩家名称出现在服务器列表中。" }, + "app.settings.game-options.setting.allow-server-listing.label": { "message": "服务器列表" }, + "app.settings.game-options.setting.alternate-blocks.label": { "message": "备用方块" }, + "app.settings.game-options.setting.ambient-occlusion.label": { "message": "平滑光照" }, + "app.settings.game-options.setting.ambient-volume.label": { "message": "环境" }, + "app.settings.game-options.setting.anaglyph-3d.label": { "message": "3D 红蓝立体" }, + "app.settings.game-options.setting.anisotropic-filtering.label": { "message": "各向异性过滤" }, + "app.settings.game-options.setting.anisotropy.label": { "message": "各向异性" }, + "app.settings.game-options.setting.attack-indicator.label": { "message": "攻击指示器" }, + "app.settings.game-options.setting.auto-jump.description": { "message": "自动跳过一格高的障碍物。" }, + "app.settings.game-options.setting.auto-jump.label": { "message": "自动跳跃" }, + "app.settings.game-options.setting.auto-suggestions.label": { "message": "命令建议" }, + "app.settings.game-options.setting.autosave-indicator.label": { "message": "自动保存指示器" }, + "app.settings.game-options.setting.biome-blend-radius.description": { "message": "生物群系颜色过渡的距离。" }, + "app.settings.game-options.setting.biome-blend-radius.label": { "message": "生物群系混合" }, + "app.settings.game-options.setting.blocks-volume.label": { "message": "方块" }, + "app.settings.game-options.setting.brightness.label": { "message": "亮度" }, + "app.settings.game-options.setting.cape.description": { "message": "显示玩家的披风,包括鞘翅纹理。" }, + "app.settings.game-options.setting.cape.label": { "message": "披风" }, + "app.settings.game-options.setting.chat-background-only.label": { "message": "仅聊天背景" }, + "app.settings.game-options.setting.chat-colors.label": { "message": "聊天颜色" }, + "app.settings.game-options.setting.chat-delay.label": { "message": "聊天延迟" }, + "app.settings.game-options.setting.chat-line-spacing.label": { "message": "聊天行间距" }, + "app.settings.game-options.setting.chat-links-prompt.description": { "message": "打开聊天链接前询问" }, + "app.settings.game-options.setting.chat-links-prompt.label": { "message": "打开链接前询问" }, + "app.settings.game-options.setting.chat-links.description": { "message": "允许打开聊天中的网页链接。" }, + "app.settings.game-options.setting.chat-links.label": { "message": "网页链接" }, + "app.settings.game-options.setting.chat-opacity.description": { "message": "聊天文字的不透明度。" }, + "app.settings.game-options.setting.chat-opacity.label": { "message": "聊天不透明度" }, + "app.settings.game-options.setting.chat-preview.label": { "message": "聊天预览" }, + "app.settings.game-options.setting.chat-scale.label": { "message": "聊天缩放" }, + "app.settings.game-options.setting.chat-visibility.label": { "message": "聊天可见性" }, + "app.settings.game-options.setting.chat-width.label": { "message": "聊天宽度" }, + "app.settings.game-options.setting.chunk-fade-time.label": { "message": "区块淡出时间" }, + "app.settings.game-options.setting.cloud-range.label": { "message": "云层距离" }, + "app.settings.game-options.setting.clouds.label": { "message": "云" }, + "app.settings.game-options.setting.ctrl-click-right-click.label": { "message": "按住 Control 点击代替右键点击" }, + "app.settings.game-options.setting.cutout-leaves.label": { "message": "半透明树叶" }, + "app.settings.game-options.setting.damage-tilt.label": { "message": "受伤倾斜" }, + "app.settings.game-options.setting.dark-splash.description": { "message": "将 Mojang Studios 加载界面从红色改为黑色。" }, + "app.settings.game-options.setting.dark-splash.label": { "message": "单色徽标" }, + "app.settings.game-options.setting.darkness-pulsing.label": { "message": "黑暗脉动" }, + "app.settings.game-options.setting.debug-gui-scale.label": { "message": "调试界面缩放" }, + "app.settings.game-options.setting.directional-audio.label": { "message": "方向性音频" }, + "app.settings.game-options.setting.discrete-mouse-scroll.description": { "message": "将每次鼠标滚轮输入视为一个滚动步进。" }, + "app.settings.game-options.setting.discrete-mouse-scroll.label": { "message": "离散滚动" }, + "app.settings.game-options.setting.entity-distance.label": { "message": "实体距离" }, + "app.settings.game-options.setting.entity-shadows.label": { "message": "实体阴影" }, + "app.settings.game-options.setting.exclusive-fullscreen.label": { "message": "独占全屏" }, + "app.settings.game-options.setting.extra-telemetry.label": { "message": "可选遥测" }, + "app.settings.game-options.setting.focused-chat-height.label": { "message": "聚焦时聊天高度" }, + "app.settings.game-options.setting.force-unicode-font.label": { "message": "强制 Unicode 字体" }, + "app.settings.game-options.setting.fov-effects.label": { "message": "视野效果" }, + "app.settings.game-options.setting.fov.label": { "message": "视野" }, + "app.settings.game-options.setting.fullscreen.label": { "message": "全屏" }, + "app.settings.game-options.setting.glint-speed.label": { "message": "附魔光效速度" }, + "app.settings.game-options.setting.glint-strength.label": { "message": "附魔光效强度" }, + "app.settings.game-options.setting.graphics-backend.label": { "message": "图形后端" }, + "app.settings.game-options.setting.graphics.description": { "message": "控制画面质量和性能。" }, + "app.settings.game-options.setting.graphics.label": { "message": "画面" }, + "app.settings.game-options.setting.gui-scale.description": { "message": "游戏界面和 HUD 的大小。" }, + "app.settings.game-options.setting.gui-scale.label": { "message": "界面缩放" }, + "app.settings.game-options.setting.hat.description": { "message": "显示帽子皮肤层。" }, + "app.settings.game-options.setting.hat.label": { "message": "帽子" }, + "app.settings.game-options.setting.held-item-tooltips.label": { "message": "手持物品提示" }, + "app.settings.game-options.setting.hide-lightning-flashes.label": { "message": "隐藏闪电闪光" }, + "app.settings.game-options.setting.hide-matched-names.label": { "message": "隐藏匹配的名称" }, + "app.settings.game-options.setting.hide-server-address.label": { "message": "隐藏服务器地址" }, + "app.settings.game-options.setting.hide-splash-texts.label": { "message": "隐藏启动标语" }, + "app.settings.game-options.setting.high-contrast-outline.label": { "message": "高对比度方块轮廓" }, + "app.settings.game-options.setting.high-contrast.description": { "message": "增强界面元素的对比度。" }, + "app.settings.game-options.setting.high-contrast.label": { "message": "高对比度" }, + "app.settings.game-options.setting.hostile-volume.label": { "message": "敌对生物" }, + "app.settings.game-options.setting.improved-transparency.label": { "message": "改进透明度" }, + "app.settings.game-options.setting.in-game-notifications.label": { "message": "游戏内通知" }, + "app.settings.game-options.setting.inactivity-framerate-limit.label": { "message": "降低帧率" }, + "app.settings.game-options.setting.invert-horizontal-mouse.label": { "message": "反转水平鼠标" }, + "app.settings.game-options.setting.invert-mouse.description": { "message": "反转垂直鼠标移动。" }, + "app.settings.game-options.setting.invert-mouse.label": { "message": "反转鼠标" }, + "app.settings.game-options.setting.jacket.description": { "message": "显示夹克皮肤层。" }, + "app.settings.game-options.setting.jacket.label": { "message": "夹克" }, + "app.settings.game-options.setting.japanese-glyph-variants.label": { "message": "日文字符变体" }, + "app.settings.game-options.setting.key-advancements.label": { "message": "进度" }, + "app.settings.game-options.setting.key-attack.label": { "message": "攻击" }, + "app.settings.game-options.setting.key-back.label": { "message": "后退" }, + "app.settings.game-options.setting.key-chat.label": { "message": "打开聊天" }, + "app.settings.game-options.setting.key-command.label": { "message": "命令" }, + "app.settings.game-options.setting.key-debug-advanced-tooltips.label": { "message": "显示高级提示" }, + "app.settings.game-options.setting.key-debug-chunk-borders.label": { "message": "显示区块边界" }, + "app.settings.game-options.setting.key-debug-clear-chat.label": { "message": "清空聊天" }, + "app.settings.game-options.setting.key-debug-copy-location.label": { "message": "复制位置" }, + "app.settings.game-options.setting.key-debug-copy-recreate-command.label": { "message": "复制重建命令" }, + "app.settings.game-options.setting.key-debug-crash.label": { "message": "触发调试崩溃" }, + "app.settings.game-options.setting.key-debug-dump-dynamic-textures.label": { "message": "转储动态纹理" }, + "app.settings.game-options.setting.key-debug-dump-version.label": { "message": "转储版本" }, + "app.settings.game-options.setting.key-debug-focus-pause.label": { "message": "失去焦点时暂停" }, + "app.settings.game-options.setting.key-debug-fps-charts.label": { "message": "FPS 图表" }, + "app.settings.game-options.setting.key-debug-hitboxes.label": { "message": "显示碰撞箱" }, + "app.settings.game-options.setting.key-debug-improved-transparency.label": { "message": "改进透明度调试视图" }, + "app.settings.game-options.setting.key-debug-lightmap-texture.label": { "message": "光照贴图纹理" }, + "app.settings.game-options.setting.key-debug-modifier.label": { "message": "调试修饰键" }, + "app.settings.game-options.setting.key-debug-network-charts.label": { "message": "网络图表" }, + "app.settings.game-options.setting.key-debug-options.label": { "message": "调试选项" }, + "app.settings.game-options.setting.key-debug-overlay.label": { "message": "调试覆盖层" }, + "app.settings.game-options.setting.key-debug-profiling-chart.label": { "message": "性能分析图表" }, + "app.settings.game-options.setting.key-debug-profiling.label": { "message": "开始性能分析" }, + "app.settings.game-options.setting.key-debug-reload-chunks.label": { "message": "重新加载区块" }, + "app.settings.game-options.setting.key-debug-reload-resource-packs.label": { "message": "重新加载资源包" }, + "app.settings.game-options.setting.key-debug-spectate.label": { "message": "旁观实体" }, + "app.settings.game-options.setting.key-debug-switch-game-mode.label": { "message": "切换游戏模式" }, + "app.settings.game-options.setting.key-drop.label": { "message": "丢弃物品" }, + "app.settings.game-options.setting.key-forward.label": { "message": "前进" }, + "app.settings.game-options.setting.key-friends.label": { "message": "好友" }, + "app.settings.game-options.setting.key-fullscreen.label": { "message": "切换全屏" }, + "app.settings.game-options.setting.key-hotbar-1.label": { "message": "快捷栏 1" }, + "app.settings.game-options.setting.key-hotbar-2.label": { "message": "快捷栏 2" }, + "app.settings.game-options.setting.key-hotbar-3.label": { "message": "快捷栏 3" }, + "app.settings.game-options.setting.key-hotbar-4.label": { "message": "快捷栏 4" }, + "app.settings.game-options.setting.key-hotbar-5.label": { "message": "快捷栏 5" }, + "app.settings.game-options.setting.key-hotbar-6.label": { "message": "快捷栏 6" }, + "app.settings.game-options.setting.key-hotbar-7.label": { "message": "快捷栏 7" }, + "app.settings.game-options.setting.key-hotbar-8.label": { "message": "快捷栏 8" }, + "app.settings.game-options.setting.key-hotbar-9.label": { "message": "快捷栏 9" }, + "app.settings.game-options.setting.key-inventory.label": { "message": "物品栏" }, + "app.settings.game-options.setting.key-jump.label": { "message": "跳跃" }, + "app.settings.game-options.setting.key-left.label": { "message": "向左平移" }, + "app.settings.game-options.setting.key-load-toolbar.label": { "message": "加载工具栏" }, + "app.settings.game-options.setting.key-perspective.label": { "message": "切换视角" }, + "app.settings.game-options.setting.key-pick-item.label": { "message": "选取方块" }, + "app.settings.game-options.setting.key-player-list.label": { "message": "玩家列表" }, + "app.settings.game-options.setting.key-quick-actions.label": { "message": "快捷操作" }, + "app.settings.game-options.setting.key-right.label": { "message": "向右平移" }, + "app.settings.game-options.setting.key-save-toolbar.label": { "message": "保存工具栏" }, + "app.settings.game-options.setting.key-screenshot.label": { "message": "截图" }, + "app.settings.game-options.setting.key-smooth-camera.label": { "message": "切换电影视角" }, + "app.settings.game-options.setting.key-sneak.label": { "message": "潜行" }, + "app.settings.game-options.setting.key-social-interactions.label": { "message": "社交互动" }, + "app.settings.game-options.setting.key-spectator-hotbar.label": { "message": "旁观者快捷栏" }, + "app.settings.game-options.setting.key-spectator-outlines.label": { "message": "高亮旁观者" }, + "app.settings.game-options.setting.key-sprint.label": { "message": "疾跑" }, + "app.settings.game-options.setting.key-swap-offhand.label": { "message": "交换副手" }, + "app.settings.game-options.setting.key-toggle-gui.label": { "message": "切换 HUD" }, + "app.settings.game-options.setting.key-toggle-spectator-shader.label": { "message": "切换旁观者着色器" }, + "app.settings.game-options.setting.key-use.label": { "message": "使用物品" }, + "app.settings.game-options.setting.language.label": { "message": "语言" }, + "app.settings.game-options.setting.left-pants-leg.label": { "message": "左裤腿" }, + "app.settings.game-options.setting.left-sleeve.label": { "message": "左袖" }, + "app.settings.game-options.setting.legacy-framerate-limit.label": { "message": "帧率上限" }, + "app.settings.game-options.setting.legacy-view-distance.label": { "message": "视距" }, + "app.settings.game-options.setting.mac-fullscreen-menu.label": { "message": "全屏时显示 macOS 菜单" }, + "app.settings.game-options.setting.main-hand.description": { "message": "选择主手为左手还是右手。" }, + "app.settings.game-options.setting.main-hand.label": { "message": "主手" }, + "app.settings.game-options.setting.master-volume.label": { "message": "主音量" }, + "app.settings.game-options.setting.max-framerate.label": { "message": "最大帧率" }, + "app.settings.game-options.setting.menu-background-blur.label": { "message": "菜单背景模糊" }, + "app.settings.game-options.setting.mipmap-levels.description": { "message": "平滑远处纹理。" }, + "app.settings.game-options.setting.mipmap-levels.label": { "message": "Mipmap 级别" }, + "app.settings.game-options.setting.mouse-wheel-sensitivity.label": { "message": "鼠标滚轮灵敏度" }, + "app.settings.game-options.setting.music-frequency.label": { "message": "音乐频率" }, + "app.settings.game-options.setting.music-toast.description": { "message": "选择是否在暂停菜单和提示中显示音乐标题。" }, + "app.settings.game-options.setting.music-toast.label": { "message": "音乐通知" }, + "app.settings.game-options.setting.music-volume.label": { "message": "音乐" }, + "app.settings.game-options.setting.narrator-hotkey.label": { "message": "旁白快捷键" }, + "app.settings.game-options.setting.narrator.description": { "message": "选择旁白朗读的内容。" }, + "app.settings.game-options.setting.narrator.label": { "message": "旁白" }, + "app.settings.game-options.setting.neutral-volume.label": { "message": "友好生物" }, + "app.settings.game-options.setting.notification-time.description": { "message": "提示通知保持显示的时长。" }, + "app.settings.game-options.setting.notification-time.label": { "message": "通知时长" }, + "app.settings.game-options.setting.operator-items-tab.label": { "message": "管理员物品栏标签" }, + "app.settings.game-options.setting.panorama-speed.label": { "message": "全景图速度" }, + "app.settings.game-options.setting.particles.label": { "message": "粒子" }, + "app.settings.game-options.setting.players-volume.label": { "message": "玩家" }, + "app.settings.game-options.setting.prioritize-chunk-updates.label": { "message": "区块更新优先级" }, + "app.settings.game-options.setting.quit-shortcuts.label": { "message": "退出快捷键" }, + "app.settings.game-options.setting.raw-mouse-input.label": { "message": "原始鼠标输入" }, + "app.settings.game-options.setting.realms-notifications.label": { "message": "Realms 通知" }, + "app.settings.game-options.setting.record-volume.label": { "message": "唱片机/音符盒" }, + "app.settings.game-options.setting.reduced-debug-info.label": { "message": "减少调试信息" }, + "app.settings.game-options.setting.render-distance.label": { "message": "渲染距离" }, + "app.settings.game-options.setting.right-pants-leg.label": { "message": "右裤腿" }, + "app.settings.game-options.setting.right-sleeve.label": { "message": "右袖" }, + "app.settings.game-options.setting.rotate-with-minecart.label": { "message": "随矿车旋转" }, + "app.settings.game-options.setting.save-chat-drafts.label": { "message": "保存聊天草稿" }, + "app.settings.game-options.setting.screen-effects.label": { "message": "屏幕效果" }, + "app.settings.game-options.setting.secure-chat-only.label": { "message": "仅显示安全聊天" }, + "app.settings.game-options.setting.sensitivity.label": { "message": "鼠标灵敏度" }, + "app.settings.game-options.setting.server-textures.label": { "message": "服务器纹理" }, + "app.settings.game-options.setting.share-presence.label": { "message": "共享在线状态" }, + "app.settings.game-options.setting.simulation-distance.description": { "message": "实体更新以及方块和流体更新的距离。" }, + "app.settings.game-options.setting.simulation-distance.label": { "message": "模拟距离" }, + "app.settings.game-options.setting.snooper.label": { "message": "数据收集" }, + "app.settings.game-options.setting.sprint-window.label": { "message": "疾跑窗口" }, + "app.settings.game-options.setting.subtitles.description": { "message": "显示游戏声音的字幕。" }, + "app.settings.game-options.setting.subtitles.label": { "message": "字幕" }, + "app.settings.game-options.setting.text-background-opacity.label": { "message": "文字背景不透明度" }, + "app.settings.game-options.setting.texture-filtering.label": { "message": "纹理过滤" }, + "app.settings.game-options.setting.toggle-attack.label": { "message": "切换攻击" }, + "app.settings.game-options.setting.toggle-crouch.description": { "message": "按一次即可保持潜行。" }, + "app.settings.game-options.setting.toggle-crouch.label": { "message": "切换潜行" }, + "app.settings.game-options.setting.toggle-sprint.description": { "message": "按一次即可保持疾跑。" }, + "app.settings.game-options.setting.toggle-sprint.label": { "message": "切换疾跑" }, + "app.settings.game-options.setting.toggle-use.label": { "message": "切换使用" }, + "app.settings.game-options.setting.touchscreen.label": { "message": "触屏模式" }, + "app.settings.game-options.setting.ui-volume.label": { "message": "界面" }, + "app.settings.game-options.setting.unfocused-chat-height.label": { "message": "未聚焦时聊天高度" }, + "app.settings.game-options.setting.use-vbo.label": { "message": "使用 VBO" }, + "app.settings.game-options.setting.view-bobbing.description": { "message": "行走时为镜头添加摇摆效果。" }, + "app.settings.game-options.setting.view-bobbing.label": { "message": "视角摇摆" }, + "app.settings.game-options.setting.vignette.label": { "message": "晕影" }, + "app.settings.game-options.setting.voice-volume.label": { "message": "语音和语音朗读" }, + "app.settings.game-options.setting.vsync.description": { "message": "将帧率限制为显示器刷新率,以防止画面撕裂。" }, + "app.settings.game-options.setting.vsync.label": { "message": "垂直同步" }, + "app.settings.game-options.setting.weather-radius.label": { "message": "天气范围" }, + "app.settings.game-options.setting.weather-volume.label": { "message": "天气" }, + "app.settings.game-options.validation.changed-since-opened": { "message": "此设置已在其他位置更改,请检查后重试。" }, + "app.settings.game-options.validation.invalid-value": { "message": "请选择有效值。" }, + "app.settings.game-options.validation.missing-value": { "message": "请先选择一个值。" }, + "app.settings.game-options.validation.no-compatible-instances": { "message": "部分实例无法使用此设置" }, + "app.settings.synced-options.game-settings.conflict-text": { "message": "我们已加载最新设置。请检查更改后再次保存。" }, + "app.settings.synced-options.game-settings.conflict-title": { "message": "这些设置已在其他地方更改" }, + "app.settings.synced-options.game-settings.custom-empty": { "message": "Modrinth 在某个实例中找到模组添加的设置后,它们会显示在这里。" }, + "app.settings.synced-options.game-settings.disable-all": { "message": "取消同步全部" }, + "app.settings.synced-options.game-settings.empty": { "message": "没有匹配当前搜索或筛选条件的设置。" }, + "app.settings.synced-options.game-settings.enable-all": { "message": "同步全部" }, + "app.settings.synced-options.game-settings.load-failed": { "message": "无法加载游戏设置。" }, + "app.settings.synced-options.game-settings.loading": { "message": "正在加载设置……" }, + "app.settings.synced-options.game-settings.not-being-synced": { "message": "未同步" }, + "app.settings.synced-options.game-settings.retry": { "message": "重试" }, + "app.settings.synced-options.game-settings.save": { "message": "保存设置" }, + "app.settings.synced-options.game-settings.search": { "message": "搜索设置……" }, + "app.settings.synced-options.game-settings.sync-description": { "message": "同步的选项会应用到每个实例,并覆盖整合包中包含的值。" }, + "app.settings.synced-options.game-settings.sync-setting": { "message": "在所有实例间同步{setting}" }, + "app.settings.synced-options.game-settings.sync-title": { "message": "同步游戏设置" }, + "app.settings.synced-options.game-settings.synced-across-instances": { "message": "已在所有实例间同步" }, + "app.settings.synced-options.game-settings.title": { "message": "游戏设置" }, + "app.settings.synced-options.game-settings.unsaved-changes": { "message": "你有未保存的更改。" }, + "app.settings.synced-options.game-settings.unsync-setting": { "message": "停止在所有实例间同步{setting}" }, + "app.settings.synced-options.game-settings.value-mixed": { "message": "各实例不同" }, + "app.settings.synced-options.game-settings.value-off": { "message": "关闭" }, + "app.settings.synced-options.game-settings.value-on": { "message": "开启" }, + "app.settings.synced-options.game-settings.value-unset": { "message": "选择一个值" } } diff --git a/apps/app-frontend/src/pages/instance/components/settings-modal/instance-settings-context.ts b/apps/app-frontend/src/pages/instance/components/settings-modal/instance-settings-context.ts new file mode 100644 index 0000000..87718a2 --- /dev/null +++ b/apps/app-frontend/src/pages/instance/components/settings-modal/instance-settings-context.ts @@ -0,0 +1,2 @@ +export type { InstanceSettingsContext } from '@/providers/instance-settings' +export { injectInstanceSettings, provideInstanceSettings } from '@/providers/instance-settings' diff --git a/apps/app-frontend/src/providers/app-settings-modal.ts b/apps/app-frontend/src/providers/app-settings-modal.ts new file mode 100644 index 0000000..6f34988 --- /dev/null +++ b/apps/app-frontend/src/providers/app-settings-modal.ts @@ -0,0 +1,18 @@ +import type { InjectionKey } from 'vue' + +export type UnsavedChangesController = { + hasChanges: () => boolean + getOriginal: () => Record + getModified: () => Record + isSaving: () => boolean + reset: () => void + save: () => void | Promise +} + +export type AppSettingsModalContext = { + close: () => boolean + registerUnsavedChangesController: (controller: UnsavedChangesController | null) => void +} + +export const appSettingsModalContextKey: InjectionKey = + Symbol('appSettingsModalContext') diff --git a/apps/app-frontend/src/providers/instance-settings.ts b/apps/app-frontend/src/providers/instance-settings.ts index d6aa632..543fd81 100644 --- a/apps/app-frontend/src/providers/instance-settings.ts +++ b/apps/app-frontend/src/providers/instance-settings.ts @@ -8,7 +8,7 @@ export interface InstanceSettingsContext { offline?: boolean isMinecraftServer: Ref onUnlinked: () => void - closeModal?: () => void + closeModal?: (onAfterClose?: () => void) => void } export const [injectInstanceSettings, provideInstanceSettings] =