Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fb5cefc
Added English i18n support for NavigationMenu and update banner
ebkr Aug 7, 2025
a0fda7e
Added English i18n support for the empty mod list message and update …
ebkr Aug 7, 2025
c5b194d
Added i18n support for the SearchAndSort component
ebkr Aug 7, 2025
5b506fe
WIP: Added Date formatting support. Need to finish porting LocalModCa…
ebkr Aug 7, 2025
592dc3e
Ported LocalModCard and DonateButton to use i18n
ebkr Aug 7, 2025
cbe4208
Added ExpandableCard translation
ebkr Aug 7, 2025
7c29cee
Used translation in application update banner. Fixed visual regression.
ebkr Aug 7, 2025
0b575a9
Reverted visual regression change as it breaks scrolling
ebkr Aug 7, 2025
8cd2411
Added i18n support for loose modals in Manager.vue
ebkr Aug 8, 2025
394f289
Added i18n support for category filter modal
ebkr Aug 8, 2025
172905a
Added i18n support for sort modal
ebkr Aug 8, 2025
726288f
Added i18n support for local import modal
ebkr Aug 8, 2025
073b46e
Added i18n support for code export modal
ebkr Aug 11, 2025
7f98838
Added i18n support to DownloadProgressModal.vue
ebkr Aug 11, 2025
9705d16
Added i18n support to DownloadModVersionSelectModal
ebkr Aug 11, 2025
3c1b7e3
Added i18n support to UpdateAllInstalledModsModal
ebkr Aug 11, 2025
c13e7dc
Added i18n support to LaunchTypeModal
ebkr Aug 11, 2025
f6aea1a
Added i18n support for executable selection actions in Manager.vue
ebkr Aug 11, 2025
3dedfaf
Added i18n support for OnlinePreviewPanel
ebkr Aug 12, 2025
9620aff
Added i18n support for OnlineModView
ebkr Aug 12, 2025
90216b1
Added i18n support for OnlineModList
ebkr Aug 12, 2025
def1500
Added French translations for online view
ebkr Aug 12, 2025
9e12e77
Fixed issue after rebase in LaunchTypeModal and improved "Par" capita…
ebkr Aug 27, 2025
5c6f0cd
Fixed imports and duplicate text after rebase
ebkr Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/boot/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createI18n } from 'vue-i18n';
import messages from '../i18n';
import messages, { datetimeFormats } from '../i18n';
import { defineBoot } from '#q-app/wrappers';
// You'll need to create the src/i18n/index.js/.ts file too

Expand All @@ -9,6 +9,7 @@ const i18n = createI18n({
allowComposition: true,
legacy: false,
messages,
datetimeFormats
})

// @ts-ignore
Expand Down
12 changes: 7 additions & 5 deletions src/components/ExpandableCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
<header class='card-header is-shadowless' :id='id'>
<div class='card-header-icon mod-logo' v-if="image !== ''">
<figure class='image is-48x48 image-parent'>
<img :src='image' alt='Mod Logo' class='image-overlap'/>
<img v-if="store.state.profile.funkyMode" src='../assets/funky_mode.png' alt='Mod Logo' class='image-overlap'/>
<img :src='image' :alt="t('translations.pages.manager.installed.expandableCard.imageAltText')" class='image-overlap'/>
<img v-if="store.state.profile.funkyMode" src='../assets/funky_mode.png' :alt="t('translations.pages.manager.installed.expandableCard.funkyModeAltText')" class='image-overlap'/>
</figure>
</div>
<span ref="title" class='card-header-title'><slot name='title'></slot></span>
<slot name='other-icons'></slot>
<!-- Allow movement of mod order -->
<a v-if='showSort' class='card-header-icon handle'>
<i class="fas fa-grip-vertical" v-tooltip.left="'Drag to reorder'"></i>
<i class="fas fa-grip-vertical" :v-tooltip.left="t('translations.pages.manager.installed.expandableCard.tooltips.dragToReorder')"></i>
</a>
<a class='card-header-icon'>
<span class='icon'>
<i class='fas fa-angle-right' aria-hidden='true' v-if='!visible' v-tooltip.left="'Expand'"></i>
<i class='fas fa-angle-down' aria-hidden='true' v-if='visible' v-tooltip.left="'Collapse'"></i>
<i class='fas fa-angle-right' aria-hidden='true' v-if='!visible' v-tooltip.left="t('translations.pages.manager.installed.expandableCard.tooltips.expand')"></i>
<i class='fas fa-angle-down' aria-hidden='true' v-if='visible' v-tooltip.left="t('translations.pages.manager.installed.expandableCard.tooltips.collapse')"></i>
</span>
</a>
</header>
Expand All @@ -43,8 +43,10 @@
import { computed, onMounted, ref, watchEffect } from 'vue';
import { getStore } from '../providers/generic/store/StoreProvider';
import { State } from '../store';
import { useI18n } from "vue-i18n";

const store = getStore<State>();
const { t } = useI18n();

type ExpandableCardProps = {
image?: string;
Expand Down
7 changes: 5 additions & 2 deletions src/components/buttons/DonateButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
<ExternalLink v-if="mod && mod.getDonationLink()"
:url="mod.getDonationLink()"
class="card-footer-item"
v-tooltip.left="{content: 'Donate to the mod author', distance: 0}">
v-tooltip.left="{content: t('translations.pages.manager.installed.localModCard.tooltips.donate'), distance: 0}">
<i class='fas fa-heart margin-right margin-right--half-width'></i>
Donate
{{ t('translations.pages.manager.installed.localModCard.actions.donate') }}
</ExternalLink>
</template>

<script lang="ts" setup>
import ThunderstoreMod from '../../model/ThunderstoreMod';
import { ExternalLink } from '../../components/all';
import { useI18n } from 'vue-i18n';

const { t } = useI18n();

type DonateButtonProps = {
mod: ThunderstoreMod;
Expand Down
76 changes: 52 additions & 24 deletions src/components/importing/LocalFileImportModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,107 @@
<div>
<ModalCard id="import-mod-from-file-modal" :can-close="true" @close-modal="closeModal" :is-active="isOpen">
<template v-slot:header>
<h2 class='modal-title'>Import mod from file</h2>
<h2 class='modal-title'>
{{ t('translations.pages.manager.modals.importLocalMod.title') }}
</h2>
</template>
<template v-slot:footer v-if="fileToImport === null">
<button class="button is-info" @click="selectFile">Select file</button>
<button class="button is-info" @click="selectFile">
{{ t('translations.pages.manager.modals.importLocalMod.actions.selectFile') }}
</button>
</template>
<template v-slot:footer v-else>
<button class="button is-info" @click="importFile">Import local mod</button>
<button class="button is-info" @click="importFile">
{{ t('translations.pages.manager.modals.importLocalMod.actions.importLocalMod') }}
</button>
</template>

<template slot="body" v-if="fileToImport === null">
<template v-if="!waitingForSelection">
<p>Please select a zip or DLL to be imported.</p>
<p>Zip files that contain a manifest file will have the some information pre-filled. If a manifest is not available, this will have to be entered manually.</p>
<p>
{{ t('translations.pages.manager.modals.importLocalMod.content.instructToSelect') }}
</p>
<p>
{{ t('translations.pages.manager.modals.importLocalMod.content.dataEntryInfo') }}
</p>
</template>
<template v-else>
<p>Waiting for file. This may take a minute.</p>
<p>
{{ t('translations.pages.manager.modals.importLocalMod.content.waitingForSelection') }}
</p>
</template>
</template>

<template v-slot:body v-if="fileToImport !== null">
<div class="notification is-warning" v-if="validationMessage !== null">
<p>{{ validationMessage }}</p>
<p>{{ t(`translations.pages.manager.modals.importLocalMod.validationMessages.${validationMessage}`) }}</p>
</div>
<div class="input-group input-group--flex margin-right">
<label for="mod-name" class="non-selectable">Mod name</label>
<label for="mod-name" class="non-selectable">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.modName.label') }}
</label>
<input
v-model="modName"
id="mod-name"
class="input margin-right"
ref="mod-name"
type="text"
placeholder="Enter the name of the mod"
:placeholder="t('translations.pages.manager.modals.importLocalMod.content.form.modName.placeholder')"
autocomplete="off"
/>
</div>
<br/>
<div class="input-group input-group--flex margin-right">
<label for="mod-author" class="non-selectable">Author</label>
<label for="mod-author" class="non-selectable">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.modAuthor.label') }}
</label>
<input
v-model="modAuthor"
id="mod-author"
class="input margin-right"
ref="mod-author"
type="text"
placeholder="Enter the author name"
:placeholder="t('translations.pages.manager.modals.importLocalMod.content.form.modAuthor.placeholder')"
autocomplete="off"
/>
</div>
<br/>
<div class="input-group input-group--flex margin-right">
<label for="mod-author" class="non-selectable">Description (optional)</label>
<label for="mod-author" class="non-selectable">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.description.label') }}
</label>
<input
v-model="modDescription"
id="mod-description"
class="input margin-right"
ref="mod-description"
type="text"
placeholder="Enter a description"
:placeholder="t('translations.pages.manager.modals.importLocalMod.content.form.description.placeholder')"
autocomplete="off"
/>
</div>
<hr/>
<h3 class="title is-6">Version</h3>
<h3 class="title is-6">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.version.label') }}
</h3>
<div class="input-group input-group--flex margin-right non-selectable">
<div class="is-flex">
<div class="margin-right margin-right--half-width">
<label for="mod-version-major">Major</label>
<label for="mod-version-major">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.version.majorLabel') }}
</label>
<input id="mod-version-major" ref="mod-version" class="input margin-right" type="number" v-model="modVersionMajor" min="0" step="1" placeholder="0"/>
</div>
<div class="margin-right margin-right--half-width">
<label for="mod-version-minor">Minor</label>
<label for="mod-version-minor">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.version.minorLabel') }}
</label>
<input id="mod-version-minor" ref="mod-version" class="input margin-right" type="number" v-model="modVersionMinor" min="0" step="1" placeholder="0"/>
</div>
<div>
<label for="mod-version-patch">Patch</label>
<label for="mod-version-patch">
{{ t('translations.pages.manager.modals.importLocalMod.content.form.version.patchLabel') }}
</label>
<input id="mod-version-patch" ref="mod-version" class="input margin-right" type="number" v-model="modVersionPatch" min="0" step="1" placeholder="0"/>
</div>
</div>
Expand All @@ -101,8 +127,10 @@ import { ref, computed } from 'vue';
import { getStore } from '../../providers/generic/store/StoreProvider';
import { State } from '../../store';
import path from '../../providers/node/path/path';
import {useI18n} from "vue-i18n";
const store = getStore<State>();
const { t } = useI18n();
type LocalFileImportModalProps = {
visible: boolean;
Expand Down Expand Up @@ -284,31 +312,31 @@ async function importFile() {
switch (0) {
case modName.value.trim().length:
validationMessage.value = "The mod name must not be empty.";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to comment in an earlier PR in this stack, would it be clearer if the whole translation key was used here?

validationMessage.value = "modNameEmpty";
return;
case modAuthor.value.trim().length:
validationMessage.value = "The mod author must not be empty.";
validationMessage.value = "authorNameEmpty";
return;
}
switch (NaN) {
case Number(modVersionMajor.value):
case Number(modVersionMinor.value):
case Number(modVersionPatch.value):
validationMessage.value = "Major, minor, and patch must all be numbers.";
validationMessage.value = "nonNumericVersion";
return;
}
if (modVersionMajor.value < 0) {
validationMessage.value = "Major, minor, and patch must be whole numbers greater than 0.";
validationMessage.value = "invalidVersion";
return;
}
if (modVersionMinor.value < 0) {
validationMessage.value = "Major, minor, and patch must be whole numbers greater than 0.";
validationMessage.value = "invalidVersion";
return;
}
if (modVersionPatch.value < 0) {
validationMessage.value = "Major, minor, and patch must be whole numbers greater than 0.";
validationMessage.value = "invalidVersion";
return;
}
Expand All @@ -317,7 +345,7 @@ async function importFile() {
: null;
if (profile === null) {
validationMessage.value = "Profile is not selected";
validationMessage.value = "noProfileSelected";
return;
}
Expand Down
25 changes: 18 additions & 7 deletions src/components/modals/CategoryFilterModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import ModalCard from '../../components/ModalCard.vue';
import { computed } from 'vue';
import { getStore } from '../../providers/generic/store/StoreProvider';
import { State } from '../../store';
import {useI18n} from "vue-i18n";
const store = getStore<State>();
const { t, locale } = useI18n();
const allowNsfw = computed({
get() {
Expand Down Expand Up @@ -75,28 +77,33 @@ const unselectedCategories = computed(() => store.getters["modFilters/unselected
<template>
<ModalCard id="mod-category-filter-modal" v-show="isOpen" :is-active="isOpen" :can-close="false">
<template v-slot:header>
<h2 class="modal-title">Filter mod categories</h2>
<h2 class="modal-title">
{{ t('translations.pages.manager.online.modals.modFilter.title') }}
</h2>
</template>
<template v-slot:body>
<div class="notification is-warning margin-bottom" v-if="locale !== 'en'">
{{ t('translations.pages.manager.online.modals.modFilter.languageDisclaimer') }}
</div>
<div>
<CategorySelectorModal
title="Mods must contain at least one of these categories"
:title="t('translations.pages.manager.online.modals.modFilter.selectors.atLeastOneCategory')"
:selected-categories="selectedCategoriesCompareOne"
:selectable-categories="unselectedCategories"
@selected-category="selectCompareOneCategory"
@deselected-category="unselectCategory"
/>
<hr/>
<CategorySelectorModal
title="Mods must contain all of these categories"
:title="t('translations.pages.manager.online.modals.modFilter.selectors.allCategories')"
:selected-categories="selectedCategoriesCompareAll"
:selectable-categories="unselectedCategories"
@selected-category="selectCompareAllCategory"
@deselected-category="unselectCategory"
/>
<hr/>
<CategorySelectorModal
title="Mods cannot contain any of these categories"
:title="t('translations.pages.manager.online.modals.modFilter.selectors.noneCategories')"
:selected-categories="selectedCategoriesToExclude"
:selectable-categories="unselectedCategories"
@selected-category="selectToExcludeCategory"
Expand All @@ -113,7 +120,9 @@ const unselectedCategories = computed(() => store.getters["modFilters/unselected
type="checkbox"
:class="[{'is-dark': !isDarkTheme, 'is-white': isDarkTheme}]"
>
<label for="nsfwCheckbox">Allow NSFW (potentially explicit) mods</label>
<label for="nsfwCheckbox">
{{ t('translations.pages.manager.online.modals.modFilter.allowNsfw') }}
</label>
</div>
<div>
<input
Expand All @@ -123,13 +132,15 @@ const unselectedCategories = computed(() => store.getters["modFilters/unselected
type="checkbox"
:class="[{'is-dark': !isDarkTheme, 'is-white': isDarkTheme}]"
>
<label for="showDeprecatedCheckbox">Show deprecated mods</label>
<label for="showDeprecatedCheckbox">
{{ t('translations.pages.manager.online.modals.modFilter.showDeprecated') }}
</label>
</div>
</div>
</template>
<template v-slot:footer>
<button class="button is-info" @click="close">
Apply filters
{{ t('translations.pages.manager.online.modals.modFilter.apply') }}
</button>
</template>
</ModalCard>
Expand Down
9 changes: 7 additions & 2 deletions src/components/modals/CategorySelectorModal.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script lang="ts" setup>
import {useI18n} from "vue-i18n";
const { t } = useI18n();
type CategorySelectorModalProps = {
title: string;
Expand Down Expand Up @@ -27,7 +30,7 @@ function emitDeselected(key: string) {
<div class="input-group margin-bottom">
<select class="select select--content-spacing" @change="emitSelected">
<option selected disabled>
Select a category
{{ t('translations.pages.manager.modals.categorySelector.selectCategory') }}
</option>
<option v-for="(key, index) in selectableCategories" :key="`category--${key}-${index}`">
{{ key }}
Expand All @@ -46,7 +49,9 @@ function emitDeselected(key: string) {
</div>
<div class="field has-addons" v-else>
<span class="tags">
<span class="tag">No categories selected</span>
<span class="tag">
{{ t('translations.pages.manager.modals.categorySelector.noCategoriesSelected') }}
</span>
</span>
</div>
</div>
Expand Down
Loading
Loading