From 795f5939f546b97660d1b77321492302c36d4070 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Thu, 19 Mar 2026 14:47:30 +0800
Subject: [PATCH 01/11] Make dashboard page localizable
---
apps/frontend/src/locales/en-US/index.json | 252 +++++++++++++
.../src/pages/dashboard/affiliate-links.vue | 11 +-
.../src/pages/dashboard/analytics.vue | 6 +-
.../src/pages/dashboard/collections.vue | 57 ++-
apps/frontend/src/pages/dashboard/index.vue | 85 +++--
.../src/pages/dashboard/notifications.vue | 119 ++++++-
.../src/pages/dashboard/organizations.vue | 44 ++-
.../frontend/src/pages/dashboard/projects.vue | 330 +++++++++++++-----
.../src/pages/dashboard/report/[id].vue | 21 +-
apps/frontend/src/pages/dashboard/reports.vue | 19 +-
.../src/pages/dashboard/revenue/transfers.vue | 105 +++++-
11 files changed, 883 insertions(+), 166 deletions(-)
diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json
index 3c66ea3398..ce5c514bd3 100644
--- a/apps/frontend/src/locales/en-US/index.json
+++ b/apps/frontend/src/locales/en-US/index.json
@@ -554,6 +554,9 @@
"dashboard.affiliate-links.create.button": {
"message": "Create affiliate link"
},
+ "dashboard.affiliate-links.empty.no-codes": {
+ "message": "No affiliate codes found."
+ },
"dashboard.affiliate-links.error.title": {
"message": "Error loading affiliate links"
},
@@ -572,6 +575,15 @@
"dashboard.affiliate-links.search": {
"message": "Search affiliate links..."
},
+ "dashboard.analytics.from-projects": {
+ "message": "from {count} {count, plural, one {project} other {projects}}"
+ },
+ "dashboard.analytics.total-downloads": {
+ "message": "Total downloads"
+ },
+ "dashboard.analytics.total-followers": {
+ "message": "Total followers"
+ },
"dashboard.collections.button.create-new": {
"message": "Create new"
},
@@ -596,6 +608,21 @@
"dashboard.collections.long-title": {
"message": "Your collections"
},
+ "dashboard.collections.placeholder.search": {
+ "message": "Search collections..."
+ },
+ "dashboard.collections.sort.control-name": {
+ "message": "Sort by"
+ },
+ "dashboard.collections.sort.name-ascending": {
+ "message": "Name (A-Z)"
+ },
+ "dashboard.collections.sort.recently-created": {
+ "message": "Recently Created"
+ },
+ "dashboard.collections.sort.recently-updated": {
+ "message": "Recently Updated"
+ },
"dashboard.creator-tax-form-modal.confirmation.download-button": {
"message": "Download {formType}"
},
@@ -848,6 +875,189 @@
"dashboard.creator-withdraw-modal.withdraw-limit-used": {
"message": "You've used up your {withdrawLimit} withdrawal limit. You must complete a tax form to withdraw more."
},
+ "dashboard.head-title": {
+ "message": "Dashboard"
+ },
+ "dashboard.notifications.button.mark-all-as-read": {
+ "message": "Mark all as read"
+ },
+ "dashboard.notifications.button.view-history": {
+ "message": "View history"
+ },
+ "dashboard.notifications.empty.no-unread": {
+ "message": "You don't have any unread notifications."
+ },
+ "dashboard.notifications.error.loading": {
+ "message": "Error loading notifications:"
+ },
+ "dashboard.notifications.history.label": {
+ "message": "History"
+ },
+ "dashboard.notifications.history.title": {
+ "message": "Notification history"
+ },
+ "dashboard.notifications.link.see-all": {
+ "message": "See all"
+ },
+ "dashboard.notifications.link.view-history": {
+ "message": "View notification history"
+ },
+ "dashboard.notifications.link.view-more": {
+ "message": "View {extraNotifs} more {extraNotifs, plural, one {notification} other {notifications}}"
+ },
+ "dashboard.notifications.loading": {
+ "message": "Loading notifications..."
+ },
+ "dashboard.notifications.type.moderator-messages": {
+ "message": "Moderator messages"
+ },
+ "dashboard.notifications.type.organization-invites": {
+ "message": "Organization invites"
+ },
+ "dashboard.notifications.type.other": {
+ "message": "Other notifications"
+ },
+ "dashboard.notifications.type.project-updates": {
+ "message": "Project updates"
+ },
+ "dashboard.notifications.type.status-changes": {
+ "message": "Status changes"
+ },
+ "dashboard.notifications.type.team-invites": {
+ "message": "Team invites"
+ },
+ "dashboard.organizations.button.create": {
+ "message": "Create organization"
+ },
+ "dashboard.organizations.empty.cta": {
+ "message": "Make an organization!"
+ },
+ "dashboard.organizations.error.fetch": {
+ "message": "Failed to fetch organizations"
+ },
+ "dashboard.organizations.member-count": {
+ "message": "{count} {count, plural, one {member} other {members}}"
+ },
+ "dashboard.organizations.title": {
+ "message": "Organizations"
+ },
+ "dashboard.projects.bulk-edit-hint": {
+ "message": "You can edit multiple projects at once by selecting them below."
+ },
+ "dashboard.projects.bulk-edit.server-disabled": {
+ "message": "Server projects do not support bulk editing"
+ },
+ "dashboard.projects.empty": {
+ "message": "You don't have any projects yet. Click the green button above to begin."
+ },
+ "dashboard.projects.head-title": {
+ "message": "Projects"
+ },
+ "dashboard.projects.links.and-more": {
+ "message": "and {count} more..."
+ },
+ "dashboard.projects.links.button.clear-link": {
+ "message": "Clear link"
+ },
+ "dashboard.projects.links.button.edit": {
+ "message": "Edit links"
+ },
+ "dashboard.projects.links.changes-applied": {
+ "message": "Changes will be applied to {count} {count, plural, one {project} other {projects}}."
+ },
+ "dashboard.projects.links.description": {
+ "message": "Any links you specify below will be overwritten on each of the selected projects. Any you leave blank will be ignored. You can clear a link from all selected projects using the trash can button."
+ },
+ "dashboard.projects.links.discord-invite.description": {
+ "message": "An invitation link to your Discord server."
+ },
+ "dashboard.projects.links.discord-invite.label": {
+ "message": "Discord invite"
+ },
+ "dashboard.projects.links.issue-tracker.description": {
+ "message": "A place for users to report bugs, issues, and concerns about your project."
+ },
+ "dashboard.projects.links.issue-tracker.label": {
+ "message": "Issue tracker"
+ },
+ "dashboard.projects.links.placeholder.cleared": {
+ "message": "Existing link will be cleared"
+ },
+ "dashboard.projects.links.placeholder.valid-discord-url": {
+ "message": "Enter a valid Discord invite URL"
+ },
+ "dashboard.projects.links.placeholder.valid-url": {
+ "message": "Enter a valid URL"
+ },
+ "dashboard.projects.links.show-all-projects": {
+ "message": "Show all projects"
+ },
+ "dashboard.projects.links.source-code.description": {
+ "message": "A page/repository containing the source code for your project"
+ },
+ "dashboard.projects.links.source-code.label": {
+ "message": "Source code"
+ },
+ "dashboard.projects.links.wiki-page.description": {
+ "message": "A page containing information, documentation, and help for the project."
+ },
+ "dashboard.projects.links.wiki-page.label": {
+ "message": "Wiki page"
+ },
+ "dashboard.projects.notification.bulk-edit-success": {
+ "message": "Bulk edited selected project's links."
+ },
+ "dashboard.projects.project.icon-alt": {
+ "message": "Icon for {title}"
+ },
+ "dashboard.projects.project.moderator-message-aria": {
+ "message": "Project has a message from the moderators. View the project to see more."
+ },
+ "dashboard.projects.project.review-environment-metadata": {
+ "message": "Please review environment metadata"
+ },
+ "dashboard.projects.sort.ascending": {
+ "message": "Ascending"
+ },
+ "dashboard.projects.sort.descending": {
+ "message": "Descending"
+ },
+ "dashboard.projects.sort.label": {
+ "message": "Sort by"
+ },
+ "dashboard.projects.sort.option.name": {
+ "message": "Name"
+ },
+ "dashboard.projects.sort.option.status": {
+ "message": "Status"
+ },
+ "dashboard.projects.sort.option.type": {
+ "message": "Type"
+ },
+ "dashboard.projects.table.icon": {
+ "message": "Icon"
+ },
+ "dashboard.projects.table.id": {
+ "message": "ID"
+ },
+ "dashboard.projects.table.name": {
+ "message": "Name"
+ },
+ "dashboard.projects.table.status": {
+ "message": "Status"
+ },
+ "dashboard.projects.table.type": {
+ "message": "Type"
+ },
+ "dashboard.report.title": {
+ "message": "Report {id}"
+ },
+ "dashboard.reports.active-title": {
+ "message": "Active reports"
+ },
+ "dashboard.reports.title": {
+ "message": "Reports"
+ },
"dashboard.revenue.available-now": {
"message": "Available now"
},
@@ -884,6 +1094,36 @@
"dashboard.revenue.transactions.btn.download-csv": {
"message": "Download as CSV"
},
+ "dashboard.revenue.transactions.csv.header.amount": {
+ "message": "Amount"
+ },
+ "dashboard.revenue.transactions.csv.header.date": {
+ "message": "Date"
+ },
+ "dashboard.revenue.transactions.csv.header.fee": {
+ "message": "Fee"
+ },
+ "dashboard.revenue.transactions.csv.header.source": {
+ "message": "Source"
+ },
+ "dashboard.revenue.transactions.csv.header.status": {
+ "message": "Status"
+ },
+ "dashboard.revenue.transactions.csv.header.type": {
+ "message": "Type"
+ },
+ "dashboard.revenue.transactions.csv.source.mural-pay-unknown": {
+ "message": "Mural Pay ({unknown})"
+ },
+ "dashboard.revenue.transactions.csv.type.payout": {
+ "message": "Payout"
+ },
+ "dashboard.revenue.transactions.csv.type.withdrawal": {
+ "message": "Withdrawal"
+ },
+ "dashboard.revenue.transactions.head-title": {
+ "message": "Transaction history"
+ },
"dashboard.revenue.transactions.header": {
"message": "Transactions"
},
@@ -893,9 +1133,21 @@
"dashboard.revenue.transactions.none.desc": {
"message": "Your payouts and withdrawals will appear here."
},
+ "dashboard.revenue.transactions.not-applicable": {
+ "message": "N/A"
+ },
+ "dashboard.revenue.transactions.period.last-month": {
+ "message": "Last month"
+ },
+ "dashboard.revenue.transactions.period.this-month": {
+ "message": "This month"
+ },
"dashboard.revenue.transactions.see-all": {
"message": "See all"
},
+ "dashboard.revenue.transactions.year.all": {
+ "message": "All years"
+ },
"dashboard.revenue.withdraw.blocked-tin-mismatch": {
"message": "Your withdrawals are temporarily locked because your TIN or SSN didn't match IRS records. Please contact support to reset and resubmit your tax form."
},
diff --git a/apps/frontend/src/pages/dashboard/affiliate-links.vue b/apps/frontend/src/pages/dashboard/affiliate-links.vue
index 708f0d0592..9221e21c3d 100644
--- a/apps/frontend/src/pages/dashboard/affiliate-links.vue
+++ b/apps/frontend/src/pages/dashboard/affiliate-links.vue
@@ -44,7 +44,7 @@
v-else-if="!filteredAffiliates || filteredAffiliates.length === 0"
class="py-8 text-center"
>
-
No affiliate codes found.
+ {{ formatMessage(messages.noAffiliateCodesFound) }}
+ auth.value?.user ? (auth.value.user as { id: string }).id : null,
+)
const filteredAffiliates = computed(
() =>
affiliateLinks.value?.filter(
(link: Labrinth.Affiliate.Internal.AffiliateCode) =>
- link.affiliate === auth.value?.user?.id &&
+ link.affiliate === currentUserId.value &&
(filterQuery.value.trim()
? link.source_name.trim().toLowerCase().includes(filterQuery.value.trim().toLowerCase())
: true),
@@ -166,6 +169,10 @@ const messages = defineMessages({
id: 'dashboard.affiliate-links.error.title',
defaultMessage: 'Error loading affiliate links',
},
+ noAffiliateCodesFound: {
+ id: 'dashboard.affiliate-links.empty.no-codes',
+ defaultMessage: 'No affiliate codes found.',
+ },
revokeConfirmButton: {
id: 'dashboard.affiliate-links.revoke-confirm.button',
defaultMessage: 'Revoke',
diff --git a/apps/frontend/src/pages/dashboard/analytics.vue b/apps/frontend/src/pages/dashboard/analytics.vue
index 0301240f1b..3069ac7615 100644
--- a/apps/frontend/src/pages/dashboard/analytics.vue
+++ b/apps/frontend/src/pages/dashboard/analytics.vue
@@ -5,17 +5,19 @@
diff --git a/apps/frontend/src/pages/dashboard/reports.vue b/apps/frontend/src/pages/dashboard/reports.vue
index 98a9908ffc..e538b5e480 100644
--- a/apps/frontend/src/pages/dashboard/reports.vue
+++ b/apps/frontend/src/pages/dashboard/reports.vue
@@ -1,16 +1,31 @@
- Reports
+ {{ formatMessage(messages.reportsTitle) }}
diff --git a/apps/frontend/src/pages/dashboard/revenue/transfers.vue b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
index 60cf33e3ed..745ad307bc 100644
--- a/apps/frontend/src/pages/dashboard/revenue/transfers.vue
+++ b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
@@ -8,7 +8,9 @@
@@ -90,6 +92,7 @@ import {
import {
ButtonStyled,
Combobox,
+ commonMessages,
defineMessages,
EmptyState,
injectModrinthClient,
@@ -97,7 +100,6 @@ import {
useFormatMoney,
useVIntl,
} from '@modrinth/ui'
-import { capitalizeString } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
@@ -116,7 +118,7 @@ const client = injectModrinthClient()
const generatedState = useGeneratedState()
useHead({
- title: 'Transaction history - Modrinth',
+ title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
})
const { data: transactions, refetch } = useQuery({
@@ -143,7 +145,7 @@ const yearOptions = computed(() => {
return yearValues.map((year) => ({
value: year,
- label: year === 'all' ? 'All years' : String(year),
+ label: year === 'all' ? formatMessage(messages.allYears) : String(year),
}))
})
@@ -161,11 +163,11 @@ function getPeriodLabel(date) {
const now = dayjs()
if (txnDate.isSame(now, 'month')) {
- return 'This month'
+ return formatMessage(messages.thisMonth)
} else if (txnDate.isSame(now.subtract(1, 'month'), 'month')) {
- return 'Last month'
+ return formatMessage(messages.lastMonth)
} else {
- return capitalizeString(formatMonth(txnDate.toDate()))
+ return formatMonth(txnDate.toDate())
}
}
@@ -212,18 +214,28 @@ function transactionsToCSV() {
}
const newline = '\n'
- const header = ['Date', 'Type', 'Source', 'Status', 'Amount', 'Fee'].join(',')
+ const header = [
+ formatMessage(messages.csvDateHeader),
+ formatMessage(messages.csvTypeHeader),
+ formatMessage(messages.csvSourceHeader),
+ formatMessage(messages.csvStatusHeader),
+ formatMessage(messages.csvAmountHeader),
+ formatMessage(messages.csvFeeHeader),
+ ].join(',')
const rows = filteredTransactions.value.map((txn) => {
const date = dayjs(txn.created).format('YYYY-MM-DD HH:mm:ss')
- const type = txn.type === 'withdrawal' ? 'Withdrawal' : 'Payout'
+ const type =
+ txn.type === 'withdrawal'
+ ? formatMessage(messages.csvTypeWithdrawal)
+ : formatMessage(messages.csvTypePayout)
let methodOrSource = ''
let status = ''
let fee = ''
if (txn.type === 'withdrawal') {
- const method = txn.method_type || txn.method || 'Unknown'
+ const method = txn.method_type || txn.method || 'unknown'
switch (method) {
case 'paypal':
methodOrSource = 'PayPal'
@@ -249,15 +261,20 @@ function transactionsToCSV() {
break
}
}
- methodOrSource = 'Mural Pay (Unknown)'
+ methodOrSource = formatMessage(messages.muralPayUnknown, {
+ unknown: formatMessage(commonMessages.unknownLabel),
+ })
break
default:
- methodOrSource = method.charAt(0).toUpperCase() + method.slice(1)
+ methodOrSource =
+ method === 'unknown'
+ ? formatMessage(commonMessages.unknownLabel)
+ : method.charAt(0).toUpperCase() + method.slice(1)
}
status = txn.status
? txn.status.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
- : 'Unknown'
+ : formatMessage(commonMessages.unknownLabel)
fee = txn.fee ? Number(txn.fee).toFixed(2) : '0.00'
} else {
@@ -266,9 +283,9 @@ function transactionsToCSV() {
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
- : 'Unknown'
- status = 'N/A'
- fee = 'N/A'
+ : formatMessage(commonMessages.unknownLabel)
+ status = formatMessage(messages.notApplicable)
+ fee = formatMessage(messages.notApplicable)
}
const amount = Number(txn.amount).toFixed(2)
@@ -322,6 +339,10 @@ const messages = defineMessages({
id: 'dashboard.revenue.transactions.header',
defaultMessage: 'Transactions',
},
+ headTitle: {
+ id: 'dashboard.revenue.transactions.head-title',
+ defaultMessage: 'Transaction history',
+ },
received: {
id: 'dashboard.revenue.stats.received',
defaultMessage: 'Received',
@@ -346,5 +367,57 @@ const messages = defineMessages({
id: 'dashboard.revenue.transactions.btn.download-csv',
defaultMessage: 'Download as CSV',
},
+ allYears: {
+ id: 'dashboard.revenue.transactions.year.all',
+ defaultMessage: 'All years',
+ },
+ thisMonth: {
+ id: 'dashboard.revenue.transactions.period.this-month',
+ defaultMessage: 'This month',
+ },
+ lastMonth: {
+ id: 'dashboard.revenue.transactions.period.last-month',
+ defaultMessage: 'Last month',
+ },
+ notApplicable: {
+ id: 'dashboard.revenue.transactions.not-applicable',
+ defaultMessage: 'N/A',
+ },
+ csvDateHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.date',
+ defaultMessage: 'Date',
+ },
+ csvTypeHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.type',
+ defaultMessage: 'Type',
+ },
+ csvSourceHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.source',
+ defaultMessage: 'Source',
+ },
+ csvStatusHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.status',
+ defaultMessage: 'Status',
+ },
+ csvAmountHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.amount',
+ defaultMessage: 'Amount',
+ },
+ csvFeeHeader: {
+ id: 'dashboard.revenue.transactions.csv.header.fee',
+ defaultMessage: 'Fee',
+ },
+ csvTypeWithdrawal: {
+ id: 'dashboard.revenue.transactions.csv.type.withdrawal',
+ defaultMessage: 'Withdrawal',
+ },
+ csvTypePayout: {
+ id: 'dashboard.revenue.transactions.csv.type.payout',
+ defaultMessage: 'Payout',
+ },
+ muralPayUnknown: {
+ id: 'dashboard.revenue.transactions.csv.source.mural-pay-unknown',
+ defaultMessage: 'Mural Pay ({unknown})',
+ },
})
From b9f7b6b0d4f017046e3584f016a2f44232d06527 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Fri, 20 Mar 2026 16:56:08 +0800
Subject: [PATCH 02/11] dashboard sidebar
---
apps/frontend/src/pages/dashboard.vue | 59 ++++++++++++++++++++++-----
1 file changed, 49 insertions(+), 10 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard.vue b/apps/frontend/src/pages/dashboard.vue
index de3797c3d7..9f380c36c6 100644
--- a/apps/frontend/src/pages/dashboard.vue
+++ b/apps/frontend/src/pages/dashboard.vue
@@ -3,26 +3,26 @@
@@ -43,7 +43,7 @@ import {
OrganizationIcon,
ReportIcon,
} from '@modrinth/assets'
-import { commonMessages, useVIntl } from '@modrinth/ui'
+import { defineMessages, commonMessages, useVIntl } from '@modrinth/ui'
import { type User, UserBadge } from '@modrinth/utils'
import NavStack from '~/components/ui/NavStack.vue'
@@ -56,6 +56,45 @@ const isAffiliate = computed(() => {
const { formatMessage } = useVIntl()
+const messages = defineMessages({
+ dashboard: {
+ id: 'dashboard.sidebar.label.dashboard',
+ defaultMessage: 'Dashboard',
+ },
+ overview: {
+ id: 'dashboard.sidebar.label.overview',
+ defaultMessage: 'Overview',
+ },
+ notifications: {
+ id: 'dashboard.sidebar.label.notifications',
+ defaultMessage: 'Notifications',
+ },
+ activeReports: {
+ id: 'dashboard.sidebar.label.activeReports',
+ defaultMessage: 'Active reports',
+ },
+ creators: {
+ id: 'dashboard.sidebar.label.creators',
+ defaultMessage: 'Creators',
+ },
+ projects: {
+ id: 'dashboard.sidebar.label.projects',
+ defaultMessage: 'Projects',
+ },
+ organizations: {
+ id: 'dashboard.sidebar.label.organizations',
+ defaultMessage: 'Organizations',
+ },
+ analytics: {
+ id: 'dashboard.sidebar.label.analytics',
+ defaultMessage: 'Analytics',
+ },
+ revenue: {
+ id: 'dashboard.sidebar.label.revenue',
+ defaultMessage: 'Revenue',
+ },
+})
+
definePageMeta({
middleware: 'auth',
})
From df2919d87268cc985c497ec68fc4890b4b8bcea6 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Tue, 24 Mar 2026 20:35:12 +0800
Subject: [PATCH 03/11] prepr:frontend
---
apps/frontend/src/locales/en-US/index.json | 33 +++++++++++++++----
apps/frontend/src/pages/dashboard.vue | 33 +++++++++++++++----
.../src/pages/dashboard/collections.vue | 6 +---
.../frontend/src/pages/dashboard/projects.vue | 6 +---
.../src/pages/dashboard/revenue/index.vue | 30 +++++++++++++----
5 files changed, 80 insertions(+), 28 deletions(-)
diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json
index ce5c514bd3..6d07fb2a77 100644
--- a/apps/frontend/src/locales/en-US/index.json
+++ b/apps/frontend/src/locales/en-US/index.json
@@ -611,9 +611,6 @@
"dashboard.collections.placeholder.search": {
"message": "Search collections..."
},
- "dashboard.collections.sort.control-name": {
- "message": "Sort by"
- },
"dashboard.collections.sort.name-ascending": {
"message": "Name (A-Z)"
},
@@ -1022,9 +1019,6 @@
"dashboard.projects.sort.descending": {
"message": "Descending"
},
- "dashboard.projects.sort.label": {
- "message": "Sort by"
- },
"dashboard.projects.sort.option.name": {
"message": "Name"
},
@@ -1160,6 +1154,33 @@
"dashboard.revenue.withdraw.header": {
"message": "Withdraw"
},
+ "dashboard.sidebar.label.activeReports": {
+ "message": "Active reports"
+ },
+ "dashboard.sidebar.label.analytics": {
+ "message": "Analytics"
+ },
+ "dashboard.sidebar.label.creators": {
+ "message": "Creators"
+ },
+ "dashboard.sidebar.label.dashboard": {
+ "message": "Dashboard"
+ },
+ "dashboard.sidebar.label.notifications": {
+ "message": "Notifications"
+ },
+ "dashboard.sidebar.label.organizations": {
+ "message": "Organizations"
+ },
+ "dashboard.sidebar.label.overview": {
+ "message": "Overview"
+ },
+ "dashboard.sidebar.label.projects": {
+ "message": "Projects"
+ },
+ "dashboard.sidebar.label.revenue": {
+ "message": "Revenue"
+ },
"dashboard.withdraw.completion.account": {
"message": "Account"
},
diff --git a/apps/frontend/src/pages/dashboard.vue b/apps/frontend/src/pages/dashboard.vue
index 9f380c36c6..2be324416d 100644
--- a/apps/frontend/src/pages/dashboard.vue
+++ b/apps/frontend/src/pages/dashboard.vue
@@ -5,8 +5,16 @@
:items="[
{ type: 'heading', label: formatMessage(messages.dashboard) },
{ link: '/dashboard', label: formatMessage(messages.overview), icon: DashboardIcon },
- { link: '/dashboard/notifications', label: formatMessage(messages.notifications), icon: NotificationsIcon },
- { link: '/dashboard/reports', label: formatMessage(messages.activeReports), icon: ReportIcon },
+ {
+ link: '/dashboard/notifications',
+ label: formatMessage(messages.notifications),
+ icon: NotificationsIcon,
+ },
+ {
+ link: '/dashboard/reports',
+ label: formatMessage(messages.activeReports),
+ icon: ReportIcon,
+ },
{
link: '/dashboard/collections',
label: formatMessage(commonMessages.collectionsLabel),
@@ -14,15 +22,28 @@
},
{ type: 'heading', label: formatMessage(messages.creators) },
{ link: '/dashboard/projects', label: formatMessage(messages.projects), icon: ListIcon },
- { link: '/dashboard/organizations', label: formatMessage(messages.organizations), icon: OrganizationIcon },
- { link: '/dashboard/analytics', label: formatMessage(messages.analytics), icon: ChartIcon },
+ {
+ link: '/dashboard/organizations',
+ label: formatMessage(messages.organizations),
+ icon: OrganizationIcon,
+ },
+ {
+ link: '/dashboard/analytics',
+ label: formatMessage(messages.analytics),
+ icon: ChartIcon,
+ },
{
link: '/dashboard/affiliate-links',
label: formatMessage(commonMessages.affiliateLinksButton),
icon: AffiliateIcon,
shown: !!isAffiliate,
},
- { link: '/dashboard/revenue', label: formatMessage(messages.revenue), icon: CurrencyIcon, matchNested: true },
+ {
+ link: '/dashboard/revenue',
+ label: formatMessage(messages.revenue),
+ icon: CurrencyIcon,
+ matchNested: true,
+ },
]"
/>
@@ -43,7 +64,7 @@ import {
OrganizationIcon,
ReportIcon,
} from '@modrinth/assets'
-import { defineMessages, commonMessages, useVIntl } from '@modrinth/ui'
+import { commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { type User, UserBadge } from '@modrinth/utils'
import NavStack from '~/components/ui/NavStack.vue'
diff --git a/apps/frontend/src/pages/dashboard/collections.vue b/apps/frontend/src/pages/dashboard/collections.vue
index 16026b767d..7569701ce9 100644
--- a/apps/frontend/src/pages/dashboard/collections.vue
+++ b/apps/frontend/src/pages/dashboard/collections.vue
@@ -20,7 +20,7 @@
v-slot="{ selected }"
v-model="sortBy"
class="!w-auto flex-grow md:flex-grow-0"
- :name="formatMessage(messages.sortByControlName)"
+ :name="formatMessage(commonMessages.sortByLabel)"
:options="['updated', 'created', 'name']"
:display-name="formatCollectionSortOption"
>
@@ -187,10 +187,6 @@ const messages = defineMessages({
id: 'dashboard.collections.placeholder.search',
defaultMessage: 'Search collections...',
},
- sortByControlName: {
- id: 'dashboard.collections.sort.control-name',
- defaultMessage: 'Sort by',
- },
sortRecentlyUpdated: {
id: 'dashboard.collections.sort.recently-updated',
defaultMessage: 'Recently Updated',
diff --git a/apps/frontend/src/pages/dashboard/projects.vue b/apps/frontend/src/pages/dashboard/projects.vue
index 93cf5ef01a..9815d01ad0 100644
--- a/apps/frontend/src/pages/dashboard/projects.vue
+++ b/apps/frontend/src/pages/dashboard/projects.vue
@@ -167,7 +167,7 @@
- {{ formatMessage(messages.sortByLabel) }}
+ {{ formatMessage(commonMessages.sortByLabel) }}
Date: Tue, 24 Mar 2026 20:47:45 +0800
Subject: [PATCH 04/11] don't change the keys
---
apps/frontend/src/pages/dashboard/projects.vue | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard/projects.vue b/apps/frontend/src/pages/dashboard/projects.vue
index 9815d01ad0..f76244f91c 100644
--- a/apps/frontend/src/pages/dashboard/projects.vue
+++ b/apps/frontend/src/pages/dashboard/projects.vue
@@ -485,11 +485,11 @@ const user = await useUser()
const projects = ref([])
const projectsWithMigrationWarning = ref([])
const selectedProjects = ref([])
-const sortBy = ref('name')
+const sortBy = ref('Name')
const sortOptions = computed(() => [
- { value: 'name', label: formatMessage(messages.sortOptionName) },
- { value: 'status', label: formatMessage(messages.sortOptionStatus) },
- { value: 'type', label: formatMessage(messages.sortOptionType) },
+ { value: 'Name', label: formatMessage(messages.sortOptionName) },
+ { value: 'Status', label: formatMessage(messages.sortOptionStatus) },
+ { value: 'Type', label: formatMessage(messages.sortOptionType) },
])
const descending = ref(false)
const editLinks = reactive({
@@ -571,17 +571,17 @@ function getBulkEditDisabledTooltip(project) {
function updateSort(list, sort, desc) {
let sortedArray = list
switch (sort) {
- case 'name':
+ case 'Name':
sortedArray = list.slice().sort((a, b) => a.title.localeCompare(b.title))
break
- case 'status':
+ case 'Status':
sortedArray = list.slice().sort((a, b) => {
if (a.status < b.status) return -1
if (a.status > b.status) return 1
return 0
})
break
- case 'type':
+ case 'Type':
sortedArray = list.slice().sort((a, b) => {
if (a.project_type < b.project_type) return -1
if (a.project_type > b.project_type) return 1
@@ -646,7 +646,7 @@ async function bulkEditLinks() {
await initUserProjects()
if (user.value?.projects) {
- projects.value = updateSort(user.value.projects, 'name', false)
+ projects.value = updateSort(user.value.projects, 'Name', false)
// minecraft_java_server type determined from component on projectV3
projects.value = projects.value.map((project) => {
From 31de0f8707741084d873f716bc9de742d6f91f26 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Tue, 24 Mar 2026 20:52:59 +0800
Subject: [PATCH 05/11] undo fix
---
apps/frontend/src/pages/dashboard/affiliate-links.vue | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard/affiliate-links.vue b/apps/frontend/src/pages/dashboard/affiliate-links.vue
index 9221e21c3d..a02b7fea4d 100644
--- a/apps/frontend/src/pages/dashboard/affiliate-links.vue
+++ b/apps/frontend/src/pages/dashboard/affiliate-links.vue
@@ -94,15 +94,12 @@ const {
const filterQuery = ref('')
const creatingLink = ref(false)
-const currentUserId = computed(() =>
- auth.value?.user ? (auth.value.user as { id: string }).id : null,
-)
const filteredAffiliates = computed(
() =>
affiliateLinks.value?.filter(
(link: Labrinth.Affiliate.Internal.AffiliateCode) =>
- link.affiliate === currentUserId.value &&
+ link.affiliate === auth.value?.user?.id &&
(filterQuery.value.trim()
? link.source_name.trim().toLowerCase().includes(filterQuery.value.trim().toLowerCase())
: true),
From a3cc4059569263cf7bfbd70e0f67f0074794a9cd Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Wed, 1 Apr 2026 11:10:49 +0800
Subject: [PATCH 06/11] fix any err
---
.../src/pages/dashboard/collections.vue | 8 +---
.../src/pages/dashboard/notifications.vue | 43 +------------------
.../frontend/src/pages/dashboard/projects.vue | 15 +------
3 files changed, 5 insertions(+), 61 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard/collections.vue b/apps/frontend/src/pages/dashboard/collections.vue
index 7569701ce9..56ae3783d0 100644
--- a/apps/frontend/src/pages/dashboard/collections.vue
+++ b/apps/frontend/src/pages/dashboard/collections.vue
@@ -42,7 +42,7 @@
@@ -245,12 +245,6 @@ const router = useNativeRouter()
const validSortOptions = ['updated', 'created', 'name']
const sortBy = ref(validSortOptions.includes(route.query.s) ? route.query.s : 'updated')
-const showFollowedProjectsCollection = computed(() =>
- formatMessage(commonMessages.followedProjectsLabel)
- .toLowerCase()
- .includes(filterQuery.value.toLowerCase()),
-)
-
function formatCollectionSortOption(option) {
if (option === 'updated') {
return formatMessage(messages.sortRecentlyUpdated)
diff --git a/apps/frontend/src/pages/dashboard/notifications.vue b/apps/frontend/src/pages/dashboard/notifications.vue
index 403d7d0598..f56d216e44 100644
--- a/apps/frontend/src/pages/dashboard/notifications.vue
+++ b/apps/frontend/src/pages/dashboard/notifications.vue
@@ -35,7 +35,7 @@
v-if="notifTypes.length > 1"
v-model="selectedType"
:items="notifTypes"
- :format-label="formatNotificationTypeLabel"
+ :format-label="(x) => (x === 'all' ? 'All' : formatProjectType(x).replace('_', ' ') + 's')"
:capitalize="false"
/>
{{ formatMessage(messages.loadingNotifications) }}
@@ -75,6 +75,7 @@ import {
Pagination,
useVIntl,
} from '@modrinth/ui'
+import { formatProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
@@ -116,30 +117,6 @@ const messages = defineMessages({
id: 'dashboard.notifications.empty.no-unread',
defaultMessage: "You don't have any unread notifications.",
},
- projectUpdatesType: {
- id: 'dashboard.notifications.type.project-updates',
- defaultMessage: 'Project updates',
- },
- teamInvitesType: {
- id: 'dashboard.notifications.type.team-invites',
- defaultMessage: 'Team invites',
- },
- organizationInvitesType: {
- id: 'dashboard.notifications.type.organization-invites',
- defaultMessage: 'Organization invites',
- },
- statusChangesType: {
- id: 'dashboard.notifications.type.status-changes',
- defaultMessage: 'Status changes',
- },
- moderatorMessagesType: {
- id: 'dashboard.notifications.type.moderator-messages',
- defaultMessage: 'Moderator messages',
- },
- otherNotificationsType: {
- id: 'dashboard.notifications.type.other',
- defaultMessage: 'Other notifications',
- },
})
const client = injectModrinthClient()
@@ -158,22 +135,6 @@ const selectedType = ref('all')
const page = ref(1)
const perPage = ref(50)
-function formatNotificationTypeLabel(type) {
- if (type === 'all') {
- return formatMessage(commonMessages.allProjectType)
- }
-
- const notificationTypeMessages = {
- project_update: messages.projectUpdatesType,
- team_invite: messages.teamInvitesType,
- organization_invite: messages.organizationInvitesType,
- status_change: messages.statusChangesType,
- moderator_message: messages.moderatorMessagesType,
- }
-
- return formatMessage(notificationTypeMessages[type] ?? messages.otherNotificationsType)
-}
-
const { data, isPending, error, refetch } = useQuery({
queryKey: computed(() => [
'user',
diff --git a/apps/frontend/src/pages/dashboard/projects.vue b/apps/frontend/src/pages/dashboard/projects.vue
index f76244f91c..f1332e4ed0 100644
--- a/apps/frontend/src/pages/dashboard/projects.vue
+++ b/apps/frontend/src/pages/dashboard/projects.vue
@@ -249,7 +249,7 @@
- {{ formatProjectTypeLabel(project) }}
+ {{ formatProjectType(getProjectTypeForUrl(project.project_type, project.loaders)) }}
@@ -308,7 +308,6 @@ import {
Checkbox,
Combobox,
commonMessages,
- commonProjectTypeTitleMessages,
CopyCode,
defineMessages,
injectNotificationManager,
@@ -318,6 +317,7 @@ import {
StyledInput,
useVIntl,
} from '@modrinth/ui'
+import { formatProjectType } from '@modrinth/utils'
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
import { getProjectTypeForUrl } from '~/helpers/projects.js'
@@ -513,17 +513,6 @@ function getLinkInputPlaceholder(clearLink, isDiscord = false) {
: formatMessage(messages.enterValidUrl)
}
-function getProjectTypeTitleMessage(type) {
- return commonProjectTypeTitleMessages[type] ?? commonProjectTypeTitleMessages.project
-}
-
-function formatProjectTypeLabel(project) {
- return formatMessage(
- getProjectTypeTitleMessage(getProjectTypeForUrl(project.project_type, project.loaders)),
- { count: 1 },
- )
-}
-
function isProjectBulkEditDisabled(project) {
return (
(project.permissions & EDIT_DETAILS) === EDIT_DETAILS ||
From 8b9f715dee3b0e5dd93a38158b92ae296f7985b0 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Wed, 1 Apr 2026 11:20:38 +0800
Subject: [PATCH 07/11] don't i18n csv
---
.../src/pages/dashboard/revenue/transfers.vue | 76 +++----------------
1 file changed, 10 insertions(+), 66 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard/revenue/transfers.vue b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
index 745ad307bc..684bd476a6 100644
--- a/apps/frontend/src/pages/dashboard/revenue/transfers.vue
+++ b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
@@ -92,13 +92,12 @@ import {
import {
ButtonStyled,
Combobox,
- commonMessages,
defineMessages,
EmptyState,
injectModrinthClient,
useFormatDateTime,
useFormatMoney,
- useVIntl,
+ useVIntl
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
@@ -214,28 +213,18 @@ function transactionsToCSV() {
}
const newline = '\n'
- const header = [
- formatMessage(messages.csvDateHeader),
- formatMessage(messages.csvTypeHeader),
- formatMessage(messages.csvSourceHeader),
- formatMessage(messages.csvStatusHeader),
- formatMessage(messages.csvAmountHeader),
- formatMessage(messages.csvFeeHeader),
- ].join(',')
+ const header = ['Date', 'Type', 'Source', 'Status', 'Amount', 'Fee'].join(',')
const rows = filteredTransactions.value.map((txn) => {
const date = dayjs(txn.created).format('YYYY-MM-DD HH:mm:ss')
- const type =
- txn.type === 'withdrawal'
- ? formatMessage(messages.csvTypeWithdrawal)
- : formatMessage(messages.csvTypePayout)
+ const type = txn.type === 'withdrawal' ? 'Withdrawal' : 'Payout'
let methodOrSource = ''
let status = ''
let fee = ''
if (txn.type === 'withdrawal') {
- const method = txn.method_type || txn.method || 'unknown'
+ const method = txn.method_type || txn.method || 'Unknown'
switch (method) {
case 'paypal':
methodOrSource = 'PayPal'
@@ -261,20 +250,15 @@ function transactionsToCSV() {
break
}
}
- methodOrSource = formatMessage(messages.muralPayUnknown, {
- unknown: formatMessage(commonMessages.unknownLabel),
- })
+ methodOrSource = 'Mural Pay (Unknown)'
break
default:
- methodOrSource =
- method === 'unknown'
- ? formatMessage(commonMessages.unknownLabel)
- : method.charAt(0).toUpperCase() + method.slice(1)
+ methodOrSource = method.charAt(0).toUpperCase() + method.slice(1)
}
status = txn.status
? txn.status.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
- : formatMessage(commonMessages.unknownLabel)
+ : 'Unknown'
fee = txn.fee ? Number(txn.fee).toFixed(2) : '0.00'
} else {
@@ -283,9 +267,9 @@ function transactionsToCSV() {
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
- : formatMessage(commonMessages.unknownLabel)
- status = formatMessage(messages.notApplicable)
- fee = formatMessage(messages.notApplicable)
+ : 'Unknown'
+ status = 'N/A'
+ fee = 'N/A'
}
const amount = Number(txn.amount).toFixed(2)
@@ -379,45 +363,5 @@ const messages = defineMessages({
id: 'dashboard.revenue.transactions.period.last-month',
defaultMessage: 'Last month',
},
- notApplicable: {
- id: 'dashboard.revenue.transactions.not-applicable',
- defaultMessage: 'N/A',
- },
- csvDateHeader: {
- id: 'dashboard.revenue.transactions.csv.header.date',
- defaultMessage: 'Date',
- },
- csvTypeHeader: {
- id: 'dashboard.revenue.transactions.csv.header.type',
- defaultMessage: 'Type',
- },
- csvSourceHeader: {
- id: 'dashboard.revenue.transactions.csv.header.source',
- defaultMessage: 'Source',
- },
- csvStatusHeader: {
- id: 'dashboard.revenue.transactions.csv.header.status',
- defaultMessage: 'Status',
- },
- csvAmountHeader: {
- id: 'dashboard.revenue.transactions.csv.header.amount',
- defaultMessage: 'Amount',
- },
- csvFeeHeader: {
- id: 'dashboard.revenue.transactions.csv.header.fee',
- defaultMessage: 'Fee',
- },
- csvTypeWithdrawal: {
- id: 'dashboard.revenue.transactions.csv.type.withdrawal',
- defaultMessage: 'Withdrawal',
- },
- csvTypePayout: {
- id: 'dashboard.revenue.transactions.csv.type.payout',
- defaultMessage: 'Payout',
- },
- muralPayUnknown: {
- id: 'dashboard.revenue.transactions.csv.source.mural-pay-unknown',
- defaultMessage: 'Mural Pay ({unknown})',
- },
})
From 0fb0477cb2c316e4ca7a941ef9105162fdbed7e5 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Wed, 1 Apr 2026 11:39:34 +0800
Subject: [PATCH 08/11] prepr:frontend
---
apps/frontend/src/locales/en-US/index.json | 48 -------------------
.../src/pages/dashboard/revenue/transfers.vue | 2 +-
2 files changed, 1 insertion(+), 49 deletions(-)
diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json
index 6d07fb2a77..bc64675ab5 100644
--- a/apps/frontend/src/locales/en-US/index.json
+++ b/apps/frontend/src/locales/en-US/index.json
@@ -905,24 +905,6 @@
"dashboard.notifications.loading": {
"message": "Loading notifications..."
},
- "dashboard.notifications.type.moderator-messages": {
- "message": "Moderator messages"
- },
- "dashboard.notifications.type.organization-invites": {
- "message": "Organization invites"
- },
- "dashboard.notifications.type.other": {
- "message": "Other notifications"
- },
- "dashboard.notifications.type.project-updates": {
- "message": "Project updates"
- },
- "dashboard.notifications.type.status-changes": {
- "message": "Status changes"
- },
- "dashboard.notifications.type.team-invites": {
- "message": "Team invites"
- },
"dashboard.organizations.button.create": {
"message": "Create organization"
},
@@ -1088,33 +1070,6 @@
"dashboard.revenue.transactions.btn.download-csv": {
"message": "Download as CSV"
},
- "dashboard.revenue.transactions.csv.header.amount": {
- "message": "Amount"
- },
- "dashboard.revenue.transactions.csv.header.date": {
- "message": "Date"
- },
- "dashboard.revenue.transactions.csv.header.fee": {
- "message": "Fee"
- },
- "dashboard.revenue.transactions.csv.header.source": {
- "message": "Source"
- },
- "dashboard.revenue.transactions.csv.header.status": {
- "message": "Status"
- },
- "dashboard.revenue.transactions.csv.header.type": {
- "message": "Type"
- },
- "dashboard.revenue.transactions.csv.source.mural-pay-unknown": {
- "message": "Mural Pay ({unknown})"
- },
- "dashboard.revenue.transactions.csv.type.payout": {
- "message": "Payout"
- },
- "dashboard.revenue.transactions.csv.type.withdrawal": {
- "message": "Withdrawal"
- },
"dashboard.revenue.transactions.head-title": {
"message": "Transaction history"
},
@@ -1127,9 +1082,6 @@
"dashboard.revenue.transactions.none.desc": {
"message": "Your payouts and withdrawals will appear here."
},
- "dashboard.revenue.transactions.not-applicable": {
- "message": "N/A"
- },
"dashboard.revenue.transactions.period.last-month": {
"message": "Last month"
},
diff --git a/apps/frontend/src/pages/dashboard/revenue/transfers.vue b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
index 684bd476a6..978e44b342 100644
--- a/apps/frontend/src/pages/dashboard/revenue/transfers.vue
+++ b/apps/frontend/src/pages/dashboard/revenue/transfers.vue
@@ -97,7 +97,7 @@ import {
injectModrinthClient,
useFormatDateTime,
useFormatMoney,
- useVIntl
+ useVIntl,
} from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import dayjs from 'dayjs'
From a265e2f8979e5cf4aac91481742f740ec90d78c1 Mon Sep 17 00:00:00 2001
From: xinyihl <1012737146@qq.com>
Date: Wed, 1 Apr 2026 11:43:35 +0800
Subject: [PATCH 09/11] fix: do not use button key
---
apps/frontend/src/pages/dashboard/analytics.vue | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/frontend/src/pages/dashboard/analytics.vue b/apps/frontend/src/pages/dashboard/analytics.vue
index 954b9ac32f..96f1865561 100644
--- a/apps/frontend/src/pages/dashboard/analytics.vue
+++ b/apps/frontend/src/pages/dashboard/analytics.vue
@@ -12,7 +12,7 @@