feat: internationalization

This commit is contained in:
2025-04-22 21:34:55 +00:00
parent 58c692a879
commit ce03668252
36 changed files with 1069 additions and 563 deletions

View File

@ -12,6 +12,9 @@ import { invoke } from '../../invoke';
import { usePkgStore, usePrfStore } from '../../stores';
import { Feature } from '../../types';
import { hasFeature, pkgKey } from '../../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const pkgs = usePkgStore();
const prf = usePrfStore();
@ -60,14 +63,18 @@ load();
<template>
<OptionCategory title="Aime">
<OptionRow
title="Aime type"
tooltip="Additional Aime plugins can be downloaded from the package store."
:title="t('cfg.aime.type')"
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.aime.modules'),
})
"
>
<Select
v-model="prf.current!.data.sgt.aime"
:options="[
{ title: 'hardware', value: 'Disabled' },
{ title: 'segatools built-in emulation', value: 'BuiltIn' },
{ title: t('cfg.hardware'), value: 'Disabled' },
{ title: t('cfg.segatools.builtIn'), value: 'BuiltIn' },
...pkgs.byFeature(Feature.Aime).map((p) => {
return {
title: pkgKey(p),
@ -83,8 +90,8 @@ load();
></Select>
</OptionRow>
<OptionRow
title="Aime code"
tooltip="Only applicable with the segatools built-in emulation or with compatible third-party packages"
:title="t('cfg.aime.code')"
:tooltip="t('cfg.aime.codeTooltip')"
v-if="prf.current!.data.sgt.aime !== 'Disabled'"
>
<InputText
@ -97,7 +104,7 @@ load();
/>
</OptionRow>
<div v-if="prf.current!.data.sgt.aime?.hasOwnProperty('AMNet')">
<OptionRow title="Server name">
<OptionRow :title="t('cfg.aime.serverName')">
<InputText
class="shrink"
size="small"
@ -106,7 +113,7 @@ load();
v-model="prf.current!.data.sgt.amnet.name"
/>
</OptionRow>
<OptionRow title="Server address">
<OptionRow :title="t('cfg.network.address')">
<InputText
class="shrink"
size="small"
@ -116,22 +123,21 @@ load();
/>
</OptionRow>
<OptionRow
title="Use AiMeDB for physical cards"
tooltip="Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab."
:title="t('cfg.aime.aimedb')"
:tooltip="t('cfg.aime.aimedbTooltip')"
>
<ToggleSwitch v-model="prf.current!.data.sgt.amnet.physical" />
</OptionRow>
</div>
<OptionRow
title="Aime serial port"
tooltip="Ports can be checked in Devices and Printers or at googlechromelabs.github.io/serial-terminal
For AIC Pico, the AIME port should be selected."
:title="t('cfg.aime.serialPort')"
:tooltip="t('cfg.aime.serialPortTooltip')"
v-if="prf.current!.data.sgt.aime === 'Disabled'"
>
<Select
v-model="prf.current!.data.sgt.aime_port"
:options="[
{ title: 'default', value: null },
{ title: t('default'), value: null },
...Object.entries(coms ?? {}).map(([title, value]) => {
return {
title,
@ -139,7 +145,7 @@ load();
};
}),
]"
placeholder="default"
:placeholder="t('default')"
option-label="title"
option-value="value"
></Select>

View File

@ -8,11 +8,14 @@ import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { invoke } from '../../invoke';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const capabilities: Ref<string[]> = ref([]);
const displayList: Ref<{ title: string; value: string }[]> = ref([
{
title: 'Primary',
title: t('cfg.display.primary'),
value: 'default',
},
]);
@ -25,7 +28,7 @@ const extraDisplayOptionsDisabled = computed(() => {
const loadDisplays = () => {
const newList = [
{
title: 'Primary',
title: t('cfg.display.primary'),
value: 'default',
},
];
@ -74,7 +77,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
<OptionCategory title="Display">
<OptionRow
v-if="capabilities.includes('display')"
title="Target display"
:title="t('cfg.display.target')"
>
<Select
v-model="prf.current!.data.display.target"
@ -108,7 +111,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
v-model="prf.current!.data.display.rez[1]"
/>
</OptionRow>
<OptionRow title="Display mode">
<OptionRow :title="t('cfg.display.mode')">
<SelectButton
v-model="prf.current!.data.display.mode"
:options="[
@ -122,7 +125,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Display rotation"
:title="t('cfg.display.rotation')"
v-if="capabilities.includes('display')"
>
<SelectButton
@ -130,12 +133,18 @@ const canSkipPrimarySwitch = game === 'ongeki';
:options="
isVertical
? [
{ title: 'Portrait', value: 90 },
{ title: 'Portrait (flipped)', value: 270 },
{ title: t('cfg.display.portrait'), value: 90 },
{
title: `${t('cfg.display.portrait')} (${t('cfg.display.flipped')})`,
value: 270,
},
]
: [
{ title: 'Landscape', value: 0 },
{ title: 'Landscape (flipped)', value: 180 },
{ title: t('cfg.display.landscape'), value: 0 },
{
title: `${t('cfg.display.landscape')} (${t('cfg.display.flipped')})`,
value: 180,
},
]
"
:allow-empty="true"
@ -147,7 +156,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
<OptionRow
v-if="capabilities.includes('display')"
class="number-input"
title="Refresh Rate"
:title="t('cfg.display.refreshRate')"
>
<InputNumber
v-if="game === 'ongeki'"
@ -173,9 +182,9 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Borderless fullscreen"
:title="t('cfg.display.borderlessFullscreen')"
v-if="capabilities.includes('display')"
tooltip="Match display resolution with the game."
:tooltip="t('cfg.display.borderlessFullscreenTooltip')"
>
<ToggleSwitch
:disabled="
@ -186,7 +195,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Skip switching primary display"
:title="t('cfg.display.dontSwitchPrimary')"
v-if="
capabilities.includes('display') &&
prf.current?.data.display.target !== 'default' &&
@ -194,7 +203,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
displayList.length > 2) &&
canSkipPrimarySwitch
"
dangerous-tooltip="Only enable this option if switching the primary display causes issues. The monitors must have a matching refresh rate."
:dangerous-tooltip="t('cfg.display.dontSwitchPrimaryTooltip')"
>
<ToggleSwitch
:disabled="extraDisplayOptionsDisabled"
@ -202,7 +211,7 @@ const canSkipPrimarySwitch = game === 'ongeki';
/>
</OptionRow>
<OptionRow
title="Display index"
:title="t('cfg.display.index')"
class="number-input"
v-if="
capabilities.includes('display') &&

View File

@ -5,27 +5,27 @@ import KeyboardKey from '../KeyboardKey.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Keyboard">
<OptionRow
title="Enable"
tooltip="Only applicable if the IO module is set to segatools built-in (keyboard) or a compatible third-party module (like mu3io.NET)"
>
<OptionCategory :title="t('cfg.keyboard.title')">
<OptionRow :title="t('enable')" :tooltip="t('cfg.keyboard.tooltip')">
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.enabled" />
</OptionRow>
<OptionRow
title="Lever mode"
:title="t('cfg.keyboard.leverMode')"
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
>
<SelectButton
v-model="prf.current!.data.keyboard!.data.use_mouse"
:options="[
{ title: 'XInput', value: false },
{ title: 'Mouse', value: true },
{ title: t('cfg.keyboard.mouse'), value: true },
]"
:allow-empty="false"
option-label="title"

View File

@ -4,18 +4,24 @@ import FileEditor from '../FileEditor.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Misc">
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
<OptionCategory :title="t('cfg.misc.title')">
<OptionRow
:title="t('cfg.misc.intel')"
:dangerous-tooltip="t('cfg.misc.intelTooltip')"
>
<ToggleSwitch v-model="prf.current!.data.sgt.intel" />
</OptionRow>
<OptionRow
title="More segatools options"
tooltip="Advanced options not covered by STARTLINER"
:title="t('cfg.misc.other')"
:tooltip="t('cfg.misc.otherTooltip')"
>
<!-- <Button icon="pi pi-refresh" size="small" /> -->
<FileEditor filename="segatools-base.ini" />

View File

@ -6,18 +6,21 @@ import FilePicker from '../FilePicker.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Network">
<OptionRow title="Network type">
<OptionCategory :title="t('cfg.network.title')">
<OptionRow :title="t('cfg.network.type')">
<SelectButton
v-model="prf.current!.data.network.network_type"
:options="[
{ title: 'Remote', value: 'Remote' },
{ title: 'Local (ARTEMiS)', value: 'Artemis' },
{ title: t('cfg.network.remote'), value: 'Remote' },
{ title: t('cfg.network.localArtemis'), value: 'Artemis' },
]"
:allow-empty="false"
option-label="title"
@ -25,8 +28,8 @@ const prf = usePrfStore();
/>
</OptionRow>
<OptionRow
v-if="prf.current!.data.network.network_type == 'Artemis'"
title="ARTEMiS path"
v-if="prf.current!.data.network.network_type === 'Artemis'"
:title="t('cfg.network.artemisPath')"
>
<FilePicker
:directory="false"
@ -47,7 +50,7 @@ const prf = usePrfStore();
</OptionRow> -->
<OptionRow
v-if="prf.current!.data.network.network_type == 'Remote'"
title="Server address"
:title="t('cfg.network.address')"
>
<InputText
class="shrink"
@ -58,7 +61,7 @@ const prf = usePrfStore();
/> </OptionRow
><OptionRow
v-if="prf.current!.data.network.network_type == 'Remote'"
title="Keychip"
:title="t('cfg.network.keychip')"
>
<InputText
class="shrink"
@ -67,7 +70,7 @@ const prf = usePrfStore();
placeholder="A123-01234567890"
v-model="prf.current!.data.network.keychip"
/> </OptionRow
><OptionRow title="Subnet">
><OptionRow :title="t('cfg.network.subnet')">
<InputText
class="shrink"
size="small"
@ -76,7 +79,7 @@ const prf = usePrfStore();
v-model="prf.current!.data.network.subnet"
/>
</OptionRow>
<OptionRow title="Address suffix">
<OptionRow :title="t('cfg.network.addrSuffix')">
<InputNumber
class="shrink"
size="small"

View File

@ -11,6 +11,9 @@ import { invoke } from '../../invoke';
import { usePkgStore, usePrfStore } from '../../stores';
import { Feature } from '../../types';
import { pkgKey } from '../../util';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const prf = usePrfStore();
const pkgs = usePkgStore();
@ -54,10 +57,10 @@ const checkSegatoolsIni = async (target: string) => {
</script>
<template>
<OptionCategory title="General">
<OptionCategory :title="t('cfg.segatools.general')">
<OptionRow
:title="names.exe"
tooltip="STARTLINER expects unpacked executables put into otherwise clean data."
:tooltip="t('cfg.segatools.targetTooltip')"
>
<FilePicker
:directory="false"
@ -104,7 +107,11 @@ const checkSegatoolsIni = async (target: string) => {
</OptionRow>
<OptionRow
:title="names.hook"
tooltip="Hooks can be downloaded from the package store."
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.hooks'),
})
"
>
<Select
v-model="prf.current!.data.sgt.hook"
@ -126,7 +133,11 @@ const checkSegatoolsIni = async (target: string) => {
</OptionRow>
<OptionRow
:title="names.io"
tooltip="IO plugins can be downloaded from the package store."
:tooltip="
t('cfg.segatools.installTooltip', {
thing: t('cfg.segatools.ioModules'),
})
"
>
<Select
v-model="prf.current!.data.sgt.io2"

View File

@ -1,92 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { useClientStore } from '../../stores';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const client = useClientStore();
const offlineModel = computed({
get() {
return client.offlineMode;
},
async set(value: boolean) {
await client.setOfflineMode(value);
},
});
const updatesModel = computed({
get() {
return client.enableAutoupdates;
},
async set(value: boolean) {
await client.setAutoupdates(value);
},
});
const verboseModel = computed({
get() {
return client.verbose;
},
async set(value: boolean) {
await client.setVerbose(value);
},
});
const themeModel = computed({
get() {
return client.theme;
},
async set(value: 'light' | 'dark' | 'system') {
await client.setTheme(value);
},
});
</script>
<template>
<OptionCategory title="STARTLINER">
<OptionRow title="UI scaling">
<SelectButton
v-model="client.scaleModel"
:options="[
{ title: 'S', value: 's' },
{ title: 'M', value: 'm' },
{ title: 'L', value: 'l' },
{ title: 'XL', value: 'xl' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
<OptionRow
:title="t('cfg.startliner.offlineMode')"
:tooltip="`${t('cfg.startliner.offlineModeTooltip')} ${t('cfg.afterRestart')}`"
>
<ToggleSwitch
:model-value="client.offlineMode"
@update:model-value="
async (v) => await client.setOfflineMode(v)
"
/>
</OptionRow>
<OptionRow
title="Offline mode"
tooltip="Disables the package store. Applies after a restart."
>
<ToggleSwitch v-model="offlineModel" />
</OptionRow>
<OptionRow title="Enable automatic updates">
<ToggleSwitch v-model="updatesModel" />
<OptionRow :title="t('cfg.startliner.autoUpdate')">
<ToggleSwitch
:model-value="client.enableAutoupdates"
@update:model-value="
async (v) => await client.setAutoupdates(v)
"
></ToggleSwitch>
</OptionRow>
<OptionRow
title="Enable detailed logs"
tooltip="Applies after a restart."
:title="t('cfg.startliner.verbose')"
:tooltip="t('cfg.afterRestart')"
>
<ToggleSwitch v-model="verboseModel" />
</OptionRow>
<OptionRow title="Theme">
<SelectButton
v-model="themeModel"
:options="[
{ title: 'System', value: 'system' },
{ title: 'Light', value: 'light' },
{ title: 'Dark', value: 'dark' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
<ToggleSwitch
:model-value="client.verbose"
@update:model-value="async (v) => await client.setVerbose(v)"
/>
</OptionRow>
</OptionCategory>