Add translations for the default page layout (#1616)

* Convert default.vue to composition API

* Add translations to default page layout

* Generate index.json

* Fix sign up page

* Generate index.json

* Fix atUserLabel

* Fix composition API port

* Follow-up fixes for #1616 (#1618)

* Re-organize default layout messages

- Group some of the messages by their appearance (like banners)
  Grouping makes avoids having to think too much about the property
  names, keeps declarations clean, and also can be minified better,
  since variable names, unlike property names, can be easily mangled.

- Remove needless type specification in property name[^1]
  It's already clear from where the message is formatted what it does,
  and if you need clarification, you can Ctrl+hover and see the type in
  the key.

- Change some keys to conform to conventions
  We generally follow the pattern location > kind?. Things like 'label'
  are unnecessary, as everything is assumed to be label by default; the
  same applies to things like 'button', which are also often unnecessary
  as they're part of the location compound.

- Remove message for handle, which should not be translatable
  It's very unlikely user handle format changes between any of the
  locales.

[^1]: Common messages are pending restructuring that would fix this

* Fix navRoutes not being computed in default layout

* Fix untranslated Get Modrinth App buttons in default layout

* Make legal disclaimer in the default layout translatable

It wouldn't make much sense to leave it untranslated since it is meant
for the end users too. It is also so small that it's unlikely to be
mistranslated.

* Extract missed legal disclaimer message from default layout

* Make SEO strings in default layout translatable

---------

Co-authored-by: Sasha Sorokin <10401817+brawaru@users.noreply.github.com>
This commit is contained in:
Prospector
2024-01-29 18:55:54 -08:00
committed by GitHub
parent 9f6e033c53
commit dda469d10e
7 changed files with 469 additions and 192 deletions

View File

@@ -5,14 +5,16 @@
class="email-nag" class="email-nag"
> >
<template v-if="auth.user.email"> <template v-if="auth.user.email">
<span>For security purposes, please verify your email address on Modrinth.</span> <span>{{ formatMessage(verifyEmailBannerMessages.title) }}</span>
<button class="btn" @click="resendVerifyEmail">Re-send verification email</button> <button class="btn" @click="resendVerifyEmail">
{{ formatMessage(verifyEmailBannerMessages.action) }}
</button>
</template> </template>
<template v-else> <template v-else>
<span>For security purposes, please enter your email on Modrinth.</span> <span>{{ formatMessage(addEmailBannerMessages.title) }}</span>
<nuxt-link class="btn" to="/settings/account"> <nuxt-link class="btn" to="/settings/account">
<SettingsIcon /> <SettingsIcon />
Visit account settings {{ formatMessage(addEmailBannerMessages.action) }}
</nuxt-link> </nuxt-link>
</template> </template>
</div> </div>
@@ -25,12 +27,10 @@
> >
<div class="site-banner__title"> <div class="site-banner__title">
<IssuesIcon /> <IssuesIcon />
<span> Youre viewing Modrinths staging environment </span> <span>{{ formatMessage(stagingBannerMessages.title) }}</span>
</div> </div>
<div class="site-banner__description"> <div class="site-banner__description">
The staging environment is running on a copy of the production Modrinth database. This is {{ formatMessage(stagingBannerMessages.description) }}
used for testing and debugging purposes, and may be running in-development versions of the
Modrinth backend or frontend newer than the production instance.
</div> </div>
<div class="site-banner__actions"> <div class="site-banner__actions">
<Button transparent icon-only :action="hideStagingBanner"><XIcon /></Button> <Button transparent icon-only :action="hideStagingBanner"><XIcon /></Button>
@@ -53,13 +53,13 @@
v-if="auth.user" v-if="auth.user"
to="/dashboard/notifications" to="/dashboard/notifications"
class="control-button button-transparent" class="control-button button-transparent"
title="Notifications" :title="formatMessage(commonMessages.notificationsLabel)"
> >
<NotificationIcon aria-hidden="true" /> <NotificationIcon aria-hidden="true" />
</nuxt-link> </nuxt-link>
<button <button
class="control-button button-transparent" class="control-button button-transparent"
title="Switch theme" :title="formatMessage(messages.changeTheme)"
@click="changeTheme" @click="changeTheme"
> >
<MoonIcon v-if="$colorMode.value === 'light'" aria-hidden="true" /> <MoonIcon v-if="$colorMode.value === 'light'" aria-hidden="true" />
@@ -78,7 +78,7 @@
<Avatar <Avatar
:src="auth.user.avatar_url" :src="auth.user.avatar_url"
class="user-icon" class="user-icon"
alt="Your avatar" :alt="formatMessage(messages.yourAvatarAlt)"
aria-hidden="true" aria-hidden="true"
circle circle
/> />
@@ -88,13 +88,15 @@
<NuxtLink class="item button-transparent" :to="`/user/${auth.user.username}`"> <NuxtLink class="item button-transparent" :to="`/user/${auth.user.username}`">
<div class="title profile-link"> <div class="title profile-link">
<div class="username">@{{ auth.user.username }}</div> <div class="username">@{{ auth.user.username }}</div>
<div class="prompt">Visit your profile</div> <div class="prompt">{{ formatMessage(messages.visitYourProfile) }}</div>
</div> </div>
</NuxtLink> </NuxtLink>
<hr class="divider" /> <hr class="divider" />
<button class="item button-transparent" @click="$refs.modal_creation.show()"> <button class="item button-transparent" @click="$refs.modal_creation.show()">
<PlusIcon class="icon" /> <PlusIcon class="icon" />
<span class="title">Create a project</span> <span class="title">
{{ formatMessage(commonMessages.createAProjectButton) }}
</span>
</button> </button>
<hr class="divider" /> <hr class="divider" />
<NuxtLink class="item button-transparent" to="/dashboard/collections"> <NuxtLink class="item button-transparent" to="/dashboard/collections">
@@ -103,15 +105,17 @@
</NuxtLink> </NuxtLink>
<NuxtLink class="item button-transparent" to="/dashboard/notifications"> <NuxtLink class="item button-transparent" to="/dashboard/notifications">
<NotificationIcon class="icon" /> <NotificationIcon class="icon" />
<span class="title">Notifications</span> <span class="title">{{
formatMessage(commonMessages.notificationsLabel)
}}</span>
</NuxtLink> </NuxtLink>
<NuxtLink class="item button-transparent" to="/dashboard"> <NuxtLink class="item button-transparent" to="/dashboard">
<ChartIcon class="icon" /> <ChartIcon class="icon" />
<span class="title">Dashboard</span> <span class="title">{{ formatMessage(commonMessages.dashboardLabel) }}</span>
</NuxtLink> </NuxtLink>
<NuxtLink class="item button-transparent" to="/settings"> <NuxtLink class="item button-transparent" to="/settings">
<SettingsIcon class="icon" /> <SettingsIcon class="icon" />
<span class="title">Settings</span> <span class="title">{{ formatMessage(commonMessages.settingsLabel) }}</span>
</NuxtLink> </NuxtLink>
<NuxtLink <NuxtLink
v-if="tags.staffRoles.includes(auth.user.role)" v-if="tags.staffRoles.includes(auth.user.role)"
@@ -119,7 +123,7 @@
to="/moderation" to="/moderation"
> >
<ModerationIcon class="icon" /> <ModerationIcon class="icon" />
<span class="title">Moderation</span> <span class="title">{{ formatMessage(commonMessages.moderationLabel) }}</span>
</NuxtLink> </NuxtLink>
<NuxtLink <NuxtLink
v-if="!cosmetics.hideModrinthAppPromos" v-if="!cosmetics.hideModrinthAppPromos"
@@ -127,25 +131,29 @@
to="/app" to="/app"
> >
<DownloadIcon class="icon" /> <DownloadIcon class="icon" />
<span class="title">Get Modrinth App</span> <span class="title">
{{ formatMessage(messages.getModrinthApp) }}
</span>
</NuxtLink> </NuxtLink>
<hr class="divider" /> <hr class="divider" />
<button class="item button-transparent" @click="logoutUser()"> <button class="item button-transparent" @click="logoutUser()">
<LogOutIcon class="icon" /> <LogOutIcon class="icon" />
<span class="dropdown-item__text">Log out</span> <span class="dropdown-item__text">
{{ formatMessage(commonMessages.signOutButton) }}
</span>
</button> </button>
</div> </div>
</div> </div>
<section v-else class="auth-prompt"> <section v-else class="auth-prompt">
<nuxt-link class="iconified-button raised-button" to="/auth/sign-in"> <nuxt-link class="iconified-button raised-button" to="/auth/sign-in">
<LogInIcon /> Sign in <LogInIcon /> {{ formatMessage(commonMessages.signInButton) }}
</nuxt-link> </nuxt-link>
<nuxt-link <nuxt-link
v-if="$route.path !== '/app' && !cosmetics.hideModrinthAppPromos" v-if="$route.path !== '/app' && !cosmetics.hideModrinthAppPromos"
class="btn btn-outline btn-primary app-btn" class="btn btn-outline btn-primary app-btn"
to="/app" to="/app"
> >
<DownloadIcon /> Get Modrinth App <DownloadIcon /> {{ formatMessage(messages.getModrinthApp) }}
</nuxt-link> </nuxt-link>
</section> </section>
</section> </section>
@@ -185,32 +193,32 @@
<Avatar <Avatar
:src="auth.user.avatar_url" :src="auth.user.avatar_url"
class="user-icon" class="user-icon"
alt="Your avatar" :alt="formatMessage(messages.yourAvatarAlt)"
aria-hidden="true" aria-hidden="true"
circle circle
/> />
<div class="account-text"> <div class="account-text">
<div>@{{ auth.user.username }}</div> <div>@{{ auth.user.username }}</div>
<div>Visit your profile</div> <div>{{ formatMessage(messages.visitYourProfile) }}</div>
</div> </div>
</NuxtLink> </NuxtLink>
<nuxt-link v-else class="iconified-button brand-button" to="/auth/sign-in"> <nuxt-link v-else class="iconified-button brand-button" to="/auth/sign-in">
<LogInIcon /> Sign in <LogInIcon /> {{ formatMessage(commonMessages.signInButton) }}
</nuxt-link> </nuxt-link>
</div> </div>
<div class="links"> <div class="links">
<template v-if="auth.user"> <template v-if="auth.user">
<button class="iconified-button danger-button" @click="logoutUser()"> <button class="iconified-button danger-button" @click="logoutUser()">
<LogOutIcon aria-hidden="true" /> <LogOutIcon aria-hidden="true" />
Log out {{ formatMessage(commonMessages.signOutButton) }}
</button> </button>
<button class="iconified-button" @click="$refs.modal_creation.show()"> <button class="iconified-button" @click="$refs.modal_creation.show()">
<PlusIcon aria-hidden="true" /> <PlusIcon aria-hidden="true" />
Create a project {{ formatMessage(commonMessages.createAProjectButton) }}
</button> </button>
<NuxtLink class="iconified-button" to="/dashboard/collections"> <NuxtLink class="iconified-button" to="/dashboard/collections">
<LibraryIcon class="icon" /> <LibraryIcon class="icon" />
Collections {{ formatMessage(commonMessages.collectionsLabel) }}
</NuxtLink> </NuxtLink>
<NuxtLink <NuxtLink
v-if="auth.user.role === 'moderator' || auth.user.role === 'admin'" v-if="auth.user.role === 'moderator' || auth.user.role === 'admin'"
@@ -218,28 +226,34 @@
to="/moderation" to="/moderation"
> >
<ModerationIcon aria-hidden="true" /> <ModerationIcon aria-hidden="true" />
Moderation {{ formatMessage(commonMessages.moderationLabel) }}
</NuxtLink> </NuxtLink>
</template> </template>
<NuxtLink class="iconified-button" to="/settings"> <NuxtLink class="iconified-button" to="/settings">
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
Settings {{ formatMessage(commonMessages.settingsLabel) }}
</NuxtLink> </NuxtLink>
<button class="iconified-button" @click="changeTheme"> <button class="iconified-button" @click="changeTheme">
<MoonIcon v-if="$colorMode.value === 'light'" class="icon" /> <MoonIcon v-if="$colorMode.value === 'light'" class="icon" />
<SunIcon v-else class="icon" /> <SunIcon v-else class="icon" />
<span class="dropdown-item__text">Change theme</span> <span class="dropdown-item__text">
{{ formatMessage(messages.changeTheme) }}
</span>
</button> </button>
</div> </div>
</div> </div>
<div class="mobile-navbar" :class="{ expanded: isBrowseMenuOpen || isMobileMenuOpen }"> <div class="mobile-navbar" :class="{ expanded: isBrowseMenuOpen || isMobileMenuOpen }">
<NuxtLink to="/" class="tab button-animation" title="Home"> <NuxtLink
to="/"
class="tab button-animation"
:title="formatMessage(navMenuMessages.home)"
>
<HomeIcon /> <HomeIcon />
</NuxtLink> </NuxtLink>
<button <button
class="tab button-animation" class="tab button-animation"
:class="{ 'router-link-exact-active': isBrowseMenuOpen }" :class="{ 'router-link-exact-active': isBrowseMenuOpen }"
title="Search" :title="formatMessage(navMenuMessages.search)"
@click="toggleBrowseMenu()" @click="toggleBrowseMenu()"
> >
<template v-if="auth.user"> <template v-if="auth.user">
@@ -247,7 +261,7 @@
</template> </template>
<template v-else> <template v-else>
<SearchIcon class="smaller" /> <SearchIcon class="smaller" />
Search {{ formatMessage(navMenuMessages.search) }}
</template> </template>
</button> </button>
<template v-if="auth.user"> <template v-if="auth.user">
@@ -257,7 +271,7 @@
:class="{ :class="{
'no-active': isMobileMenuOpen || isBrowseMenuOpen, 'no-active': isMobileMenuOpen || isBrowseMenuOpen,
}" }"
title="Notifications" :title="formatMessage(commonMessages.notificationsLabel)"
@click=" @click="
() => { () => {
isMobileMenuOpen = false isMobileMenuOpen = false
@@ -267,13 +281,17 @@
> >
<NotificationIcon /> <NotificationIcon />
</NuxtLink> </NuxtLink>
<NuxtLink to="/dashboard" class="tab button-animation" title="Dashboard"> <NuxtLink
to="/dashboard"
class="tab button-animation"
:title="formatMessage(commonMessages.dashboardLabel)"
>
<ChartIcon /> <ChartIcon />
</NuxtLink> </NuxtLink>
</template> </template>
<button <button
class="tab button-animation" class="tab button-animation"
title="Toggle Mobile Menu" :title="formatMessage(messages.toggleMenu)"
@click="toggleMobileMenu()" @click="toggleMobileMenu()"
> >
<template v-if="!auth.user"> <template v-if="!auth.user">
@@ -285,7 +303,7 @@
:src="auth.user.avatar_url" :src="auth.user.avatar_url"
class="user-icon" class="user-icon"
:class="{ expanded: isMobileMenuOpen }" :class="{ expanded: isMobileMenuOpen }"
alt="Your avatar" :alt="formatMessage(messages.yourAvatarAlt)"
aria-hidden="true" aria-hidden="true"
circle circle
/> />
@@ -300,17 +318,24 @@
</main> </main>
<footer> <footer>
<div class="logo-info" role="region" aria-label="Modrinth information"> <div class="logo-info" role="region" aria-label="Modrinth information">
<BrandTextLogo aria-hidden="true" class="text-logo" @click="developerModeIncrement()" /> <BrandTextLogo
aria-hidden="true"
class="text-logo button-base"
@click="developerModeIncrement()"
/>
<p> <p>
Modrinth is <IntlFormatted :message-id="footerMessages.openSource">
<a <template #github-link="{ children }">
:target="$external()" <a
href="https://github.com/modrinth" :target="$external()"
class="text-link" href="https://github.com/modrinth"
rel="noopener" class="text-link"
> rel="noopener"
open source</a >
>. <component :is="() => children" />
</a>
</template>
</IntlFormatted>
</p> </p>
<p> <p>
{{ config.public.owner }}/{{ config.public.slug }} {{ config.public.branch }}@<a {{ config.public.owner }}/{{ config.public.slug }} {{ config.public.branch }}@<a
@@ -331,23 +356,32 @@
<p>© Rinth, Inc.</p> <p>© Rinth, Inc.</p>
</div> </div>
<div class="links links-1" role="region" aria-label="Legal"> <div class="links links-1" role="region" aria-label="Legal">
<h4 aria-hidden="true">Company</h4> <h4 aria-hidden="true">{{ formatMessage(footerMessages.companyTitle) }}</h4>
<nuxt-link to="/legal/terms"> Terms</nuxt-link> <nuxt-link to="/legal/terms"> {{ formatMessage(footerMessages.terms) }}</nuxt-link>
<nuxt-link to="/legal/privacy"> Privacy</nuxt-link> <nuxt-link to="/legal/privacy"> {{ formatMessage(footerMessages.privacy) }}</nuxt-link>
<nuxt-link to="/legal/rules"> Rules</nuxt-link> <nuxt-link to="/legal/rules"> {{ formatMessage(footerMessages.rules) }}</nuxt-link>
<a :target="$external()" href="https://careers.modrinth.com"> <a :target="$external()" href="https://careers.modrinth.com">
Careers <span v-if="false" class="count-bubble">0</span> {{ formatMessage(footerMessages.careers) }}
<span v-if="false" class="count-bubble">0</span>
</a> </a>
</div> </div>
<div class="links links-2" role="region" aria-label="Resources"> <div class="links links-2" role="region" aria-label="Resources">
<h4 aria-hidden="true">Resources</h4> <h4 aria-hidden="true">{{ formatMessage(footerMessages.resourcesTitle) }}</h4>
<a :target="$external()" href="https://support.modrinth.com">Support</a> <a :target="$external()" href="https://support.modrinth.com">
<a :target="$external()" href="https://blog.modrinth.com">Blog</a> {{ formatMessage(footerMessages.support) }}
<a :target="$external()" href="https://docs.modrinth.com">Docs</a> </a>
<a :target="$external()" href="https://status.modrinth.com">Status</a> <a :target="$external()" href="https://blog.modrinth.com">
{{ formatMessage(footerMessages.blog) }}
</a>
<a :target="$external()" href="https://docs.modrinth.com">
{{ formatMessage(footerMessages.docs) }}
</a>
<a :target="$external()" href="https://status.modrinth.com">
{{ formatMessage(footerMessages.status) }}
</a>
</div> </div>
<div class="links links-3" role="region" aria-label="Interact"> <div class="links links-3" role="region" aria-label="Interact">
<h4 aria-hidden="true">Interact</h4> <h4 aria-hidden="true">{{ formatMessage(footerMessages.interactTitle) }}</h4>
<a rel="noopener" :target="$external()" href="https://discord.modrinth.com"> Discord </a> <a rel="noopener" :target="$external()" href="https://discord.modrinth.com"> Discord </a>
<a rel="noopener" :target="$external()" href="https://x.com/modrinth"> X (Twitter) </a> <a rel="noopener" :target="$external()" href="https://x.com/modrinth"> X (Twitter) </a>
<a rel="noopener" :target="$external()" href="https://floss.social/@modrinth"> Mastodon </a> <a rel="noopener" :target="$external()" href="https://floss.social/@modrinth"> Mastodon </a>
@@ -358,20 +392,20 @@
<div class="buttons"> <div class="buttons">
<nuxt-link class="btn btn-outline btn-primary" to="/app"> <nuxt-link class="btn btn-outline btn-primary" to="/app">
<DownloadIcon aria-hidden="true" /> <DownloadIcon aria-hidden="true" />
Get Modrinth App {{ formatMessage(messages.getModrinthApp) }}
</nuxt-link> </nuxt-link>
<button class="iconified-button raised-button" @click="changeTheme"> <button class="iconified-button raised-button" @click="changeTheme">
<MoonIcon v-if="$colorMode.value === 'light'" aria-hidden="true" /> <MoonIcon v-if="$colorMode.value === 'light'" aria-hidden="true" />
<SunIcon v-else aria-hidden="true" /> <SunIcon v-else aria-hidden="true" />
Change theme {{ formatMessage(messages.changeTheme) }}
</button> </button>
<nuxt-link class="iconified-button raised-button" to="/settings"> <nuxt-link class="iconified-button raised-button" to="/settings">
<SettingsIcon aria-hidden="true" /> <SettingsIcon aria-hidden="true" />
Settings {{ formatMessage(commonMessages.settingsLabel) }}
</nuxt-link> </nuxt-link>
</div> </div>
<div class="not-affiliated-notice"> <div class="not-affiliated-notice">
NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG. {{ formatMessage(footerMessages.legalDisclaimer) }}
</div> </div>
</footer> </footer>
</div> </div>
@@ -397,6 +431,8 @@ import ChartIcon from '~/assets/images/utils/chart.svg'
import NavRow from '~/components/ui/NavRow.vue' import NavRow from '~/components/ui/NavRow.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue' import ModalCreation from '~/components/ui/ModalCreation.vue'
import Avatar from '~/components/ui/Avatar.vue' import Avatar from '~/components/ui/Avatar.vue'
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
import { commonMessages } from '~/utils/common-messages'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
@@ -408,6 +444,131 @@ const tags = useTags()
const config = useRuntimeConfig() const config = useRuntimeConfig()
const route = useRoute() const route = useRoute()
const link = config.public.siteUrl + route.path.replace(/\/+$/, '') const link = config.public.siteUrl + route.path.replace(/\/+$/, '')
const verifyEmailBannerMessages = defineMessages({
title: {
id: 'layout.banner.verify-email.title',
defaultMessage: 'For security purposes, please verify your email address on Modrinth.',
},
action: {
id: 'layout.banner.verify-email.action',
defaultMessage: 'Re-send verification email',
},
})
const addEmailBannerMessages = defineMessages({
title: {
id: 'layout.banner.add-email.title',
defaultMessage: 'For security purposes, please enter your email on Modrinth.',
},
action: {
id: 'layout.banner.add-email.button',
defaultMessage: 'Visit account settings',
},
})
const stagingBannerMessages = defineMessages({
title: {
id: 'layout.banner.staging.title',
defaultMessage: 'Youre viewing Modrinths staging environment.',
},
description: {
id: 'layout.banner.staging.description',
defaultMessage:
'The staging environment is running on a copy of the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance.',
},
})
const navMenuMessages = defineMessages({
home: {
id: 'layout.nav.home',
defaultMessage: 'Home',
},
search: {
id: 'layout.nav.search',
defaultMessage: 'Search',
},
})
const messages = defineMessages({
visitYourProfile: {
id: 'layout.label.visit-your-profile',
defaultMessage: 'Visit your profile',
},
toggleMenu: {
id: 'layout.menu-toggle.action',
defaultMessage: 'Toggle menu',
},
yourAvatarAlt: {
id: 'layout.avatar.alt',
defaultMessage: 'Your avatar',
},
getModrinthApp: {
id: 'layout.action.get-modrinth-app',
defaultMessage: 'Get Modrinth App',
},
changeTheme: {
id: 'layout.action.change-theme',
defaultMessage: 'Change theme',
},
})
const footerMessages = defineMessages({
openSource: {
id: 'layout.footer.open-source',
defaultMessage: 'Modrinth is <github-link>open source</github-link>.',
},
companyTitle: {
id: 'layout.footer.company.title',
defaultMessage: 'Company',
},
terms: {
id: 'layout.footer.company.terms',
defaultMessage: 'Terms',
},
privacy: {
id: 'layout.footer.company.privacy',
defaultMessage: 'Privacy',
},
rules: {
id: 'layout.footer.company.rules',
defaultMessage: 'Rules',
},
careers: {
id: 'layout.footer.company.careers',
defaultMessage: 'Careers',
},
resourcesTitle: {
id: 'layout.footer.resources.title',
defaultMessage: 'Resources',
},
support: {
id: 'layout.footer.resources.support',
defaultMessage: 'Support',
},
blog: {
id: 'layout.footer.resources.blog',
defaultMessage: 'Blog',
},
docs: {
id: 'layout.footer.resources.docs',
defaultMessage: 'Docs',
},
status: {
id: 'layout.footer.resources.status',
defaultMessage: 'Status',
},
interactTitle: {
id: 'layout.footer.interact.title',
defaultMessage: 'Interact',
},
legalDisclaimer: {
id: 'layout.footer.legal-disclaimer',
defaultMessage:
'NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.',
},
})
useHead({ useHead({
link: [ link: [
{ {
@@ -416,14 +577,15 @@ useHead({
}, },
], ],
}) })
const description =
'Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. ' +
'Discover and publish projects on Modrinth with a modern, easy to use interface and API.'
useSeoMeta({ useSeoMeta({
title: 'Modrinth', title: 'Modrinth',
description, description: () =>
formatMessage({
id: 'layout.meta.description',
defaultMessage:
'Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. ' +
'Discover and publish projects on Modrinth with a modern, easy to use interface and API.',
}),
publisher: 'Modrinth', publisher: 'Modrinth',
themeColor: '#1bd96a', themeColor: '#1bd96a',
colorScheme: 'dark light', colorScheme: 'dark light',
@@ -431,7 +593,11 @@ useSeoMeta({
// OpenGraph // OpenGraph
ogTitle: 'Modrinth', ogTitle: 'Modrinth',
ogSiteName: 'Modrinth', ogSiteName: 'Modrinth',
ogDescription: 'Discover and publish Minecraft content!', ogDescription: () =>
formatMessage({
id: 'layout.meta.og-description',
defaultMessage: 'Discover and publish Minecraft content!',
}),
ogType: 'website', ogType: 'website',
ogImage: 'https://cdn.modrinth.com/modrinth-new.png', ogImage: 'https://cdn.modrinth.com/modrinth-new.png',
ogUrl: link, ogUrl: link,
@@ -441,12 +607,67 @@ useSeoMeta({
twitterSite: '@modrinth', twitterSite: '@modrinth',
}) })
let developerModeCounter = 0 const developerModeCounter = ref(0)
const isDropdownOpen = ref(false)
const isMobileMenuOpen = ref(false)
const isBrowseMenuOpen = ref(false)
const navRoutes = computed(() => [
{
label: formatMessage(getProjectTypeMessage('mod', true)),
href: '/mods',
},
{
label: formatMessage(getProjectTypeMessage('plugin', true)),
href: '/plugins',
},
{
label: formatMessage(getProjectTypeMessage('datapack', true)),
href: '/datapacks',
},
{
label: formatMessage(getProjectTypeMessage('shader', true)),
href: '/shaders',
},
{
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
href: '/resourcepacks',
},
{
label: formatMessage(getProjectTypeMessage('modpack', true)),
href: '/modpacks',
},
])
onMounted(() => {
if (window && process.client) {
window.history.scrollRestoration = 'auto'
}
runAnalytics()
})
watch(
() => route.path,
() => {
isMobileMenuOpen.value = false
isBrowseMenuOpen.value = false
if (process.client) {
document.body.style.overflowY = 'scroll'
document.body.setAttribute('tabindex', '-1')
document.body.removeAttribute('tabindex')
}
updateCurrentDate()
runAnalytics()
}
)
function developerModeIncrement() { function developerModeIncrement() {
if (developerModeCounter >= 5) { if (developerModeCounter.value >= 5) {
cosmetics.value.developerMode = !cosmetics.value.developerMode cosmetics.value.developerMode = !cosmetics.value.developerMode
developerModeCounter = 0 developerModeCounter.value = 0
if (cosmetics.value.developerMode) { if (cosmetics.value.developerMode) {
app.$notify({ app.$notify({
group: 'main', group: 'main',
@@ -463,7 +684,7 @@ function developerModeIncrement() {
}) })
} }
} else { } else {
developerModeCounter++ developerModeCounter.value++
} }
} }
@@ -471,110 +692,43 @@ async function logoutUser() {
await logout() await logout()
} }
function runAnalytics() {
const config = useRuntimeConfig()
const replacedUrl = config.public.apiBaseUrl.replace('v2/', '')
setTimeout(() => {
$fetch(`${replacedUrl}analytics/view`, {
method: 'POST',
body: {
url: window.location.href,
},
})
.then(() => {})
.catch(() => {})
})
}
function toggleMobileMenu() {
isMobileMenuOpen.value = !isMobileMenuOpen.value
if (isMobileMenuOpen.value) {
isBrowseMenuOpen.value = false
}
}
function toggleBrowseMenu() {
isBrowseMenuOpen.value = !isBrowseMenuOpen.value
if (isBrowseMenuOpen.value) {
isMobileMenuOpen.value = false
}
}
function changeTheme() {
updateTheme(app.$colorMode.value === 'dark' ? 'light' : 'dark', true)
}
function hideStagingBanner() { function hideStagingBanner() {
cosmetics.value.hideStagingBanner = true cosmetics.value.hideStagingBanner = true
saveCosmetics() saveCosmetics()
} }
</script> </script>
<script>
export default defineNuxtComponent({
data() {
return {
isDropdownOpen: false,
isMobileMenuOpen: false,
isBrowseMenuOpen: false,
registeredSkipLink: null,
hideDropdown: false,
navRoutes: [
{
label: 'Mods',
href: '/mods',
},
{
label: 'Plugins',
href: '/plugins',
},
{
label: 'Data Packs',
href: '/datapacks',
},
{
label: 'Shaders',
href: '/shaders',
},
{
label: 'Resource Packs',
href: '/resourcepacks',
},
{
label: 'Modpacks',
href: '/modpacks',
},
],
}
},
computed: {
isOnSearchPage() {
return this.navRoutes.some((route) => this.$route.path.startsWith(route.href))
},
},
watch: {
'$route.path'() {
this.isMobileMenuOpen = false
this.isBrowseMenuOpen = false
if (process.client) {
document.body.style.overflowY = 'scroll'
document.body.setAttribute('tabindex', '-1')
document.body.removeAttribute('tabindex')
}
updateCurrentDate()
this.runAnalytics()
},
},
mounted() {
if (process.client && window) {
window.history.scrollRestoration = 'auto'
}
this.runAnalytics()
},
methods: {
runAnalytics() {
const config = useRuntimeConfig()
const replacedUrl = config.public.apiBaseUrl.replace('v2/', '')
setTimeout(() => {
$fetch(`${replacedUrl}analytics/view`, {
method: 'POST',
body: {
url: window.location.href,
},
})
.then(() => {})
.catch(() => {})
})
},
toggleMobileMenu() {
this.isMobileMenuOpen = !this.isMobileMenuOpen
if (this.isMobileMenuOpen) {
this.isBrowseMenuOpen = false
}
},
toggleBrowseMenu() {
this.isBrowseMenuOpen = !this.isBrowseMenuOpen
if (this.isBrowseMenuOpen) {
this.isMobileMenuOpen = false
}
},
changeTheme() {
updateTheme(this.$colorMode.value === 'dark' ? 'light' : 'dark', true)
},
},
})
</script>
<style lang="scss"> <style lang="scss">
@import '~/assets/styles/global.scss'; @import '~/assets/styles/global.scss';

View File

@@ -65,9 +65,6 @@
"auth.sign-in.2fa.placeholder": { "auth.sign-in.2fa.placeholder": {
"message": "Enter code..." "message": "Enter code..."
}, },
"auth.sign-in.action.sign-in": {
"message": "Sign in"
},
"auth.sign-in.additional-options": { "auth.sign-in.additional-options": {
"message": "<forgot-password-link>Forgot password?</forgot-password-link> • <create-account-link>Create an account</create-account-link>" "message": "<forgot-password-link>Forgot password?</forgot-password-link> • <create-account-link>Create an account</create-account-link>"
}, },
@@ -107,9 +104,6 @@
"auth.sign-up.password.label": { "auth.sign-up.password.label": {
"message": "Password" "message": "Password"
}, },
"auth.sign-up.sign-in-option.action": {
"message": "Sign in"
},
"auth.sign-up.sign-in-option.title": { "auth.sign-up.sign-in-option.title": {
"message": "Already have an account?" "message": "Already have an account?"
}, },
@@ -179,6 +173,9 @@
"button.continue": { "button.continue": {
"message": "Continue" "message": "Continue"
}, },
"button.create-a-project": {
"message": "Create a project"
},
"button.edit": { "button.edit": {
"message": "Edit" "message": "Edit"
}, },
@@ -188,6 +185,12 @@
"button.save-changes": { "button.save-changes": {
"message": "Save changes" "message": "Save changes"
}, },
"button.sign-in": {
"message": "Sign in"
},
"button.sign-out": {
"message": "Sign out"
},
"collection.button.delete-icon": { "collection.button.delete-icon": {
"message": "Delete icon" "message": "Delete icon"
}, },
@@ -290,6 +293,9 @@
"label.created-ago": { "label.created-ago": {
"message": "Created {ago}" "message": "Created {ago}"
}, },
"label.dashboard": {
"message": "Dashboard"
},
"label.delete": { "label.delete": {
"message": "Delete" "message": "Delete"
}, },
@@ -302,6 +308,12 @@
"label.followed-projects": { "label.followed-projects": {
"message": "Followed projects" "message": "Followed projects"
}, },
"label.moderation": {
"message": "Moderation"
},
"label.notifications": {
"message": "Notifications"
},
"label.password": { "label.password": {
"message": "Password" "message": "Password"
}, },
@@ -314,6 +326,9 @@
"label.scopes": { "label.scopes": {
"message": "Scopes" "message": "Scopes"
}, },
"label.settings": {
"message": "Settings"
},
"label.title": { "label.title": {
"message": "Title" "message": "Title"
}, },
@@ -323,6 +338,90 @@
"label.visibility": { "label.visibility": {
"message": "Visibility" "message": "Visibility"
}, },
"layout.action.change-theme": {
"message": "Change theme"
},
"layout.action.get-modrinth-app": {
"message": "Get Modrinth App"
},
"layout.avatar.alt": {
"message": "Your avatar"
},
"layout.banner.add-email.button": {
"message": "Visit account settings"
},
"layout.banner.add-email.title": {
"message": "For security purposes, please enter your email on Modrinth."
},
"layout.banner.staging.description": {
"message": "The staging environment is running on a copy of the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance."
},
"layout.banner.staging.title": {
"message": "Youre viewing Modrinths staging environment."
},
"layout.banner.verify-email.action": {
"message": "Re-send verification email"
},
"layout.banner.verify-email.title": {
"message": "For security purposes, please verify your email address on Modrinth."
},
"layout.footer.company.careers": {
"message": "Careers"
},
"layout.footer.company.privacy": {
"message": "Privacy"
},
"layout.footer.company.rules": {
"message": "Rules"
},
"layout.footer.company.terms": {
"message": "Terms"
},
"layout.footer.company.title": {
"message": "Company"
},
"layout.footer.interact.title": {
"message": "Interact"
},
"layout.footer.legal-disclaimer": {
"message": "NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT."
},
"layout.footer.open-source": {
"message": "Modrinth is <github-link>open source</github-link>."
},
"layout.footer.resources.blog": {
"message": "Blog"
},
"layout.footer.resources.docs": {
"message": "Docs"
},
"layout.footer.resources.status": {
"message": "Status"
},
"layout.footer.resources.support": {
"message": "Support"
},
"layout.footer.resources.title": {
"message": "Resources"
},
"layout.label.visit-your-profile": {
"message": "Visit your profile"
},
"layout.menu-toggle.action": {
"message": "Toggle menu"
},
"layout.meta.description": {
"message": "Download Minecraft mods, plugins, datapacks, shaders, resourcepacks, and modpacks on Modrinth. Discover and publish projects on Modrinth with a modern, easy to use interface and API."
},
"layout.meta.og-description": {
"message": "Discover and publish Minecraft content!"
},
"layout.nav.home": {
"message": "Home"
},
"layout.nav.search": {
"message": "Search"
},
"notification.error.title": { "notification.error.title": {
"message": "An error occurred" "message": "An error occurred"
}, },

View File

@@ -19,7 +19,7 @@
/> />
<button class="btn btn-primary continue-btn" @click="begin2FASignIn"> <button class="btn btn-primary continue-btn" @click="begin2FASignIn">
{{ formatMessage(messages.signInButton) }} <RightArrowIcon /> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</button> </button>
</template> </template>
<template v-else> <template v-else>
@@ -84,7 +84,7 @@
<NuxtTurnstile ref="turnstile" v-model="token" class="turnstile" /> <NuxtTurnstile ref="turnstile" v-model="token" class="turnstile" />
<button class="btn btn-primary continue-btn centered-btn" @click="beginPasswordSignIn()"> <button class="btn btn-primary continue-btn centered-btn" @click="beginPasswordSignIn()">
{{ formatMessage(messages.signInButton) }} <RightArrowIcon /> {{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
</button> </button>
<div class="auth-form__additional-options"> <div class="auth-form__additional-options">
@@ -135,10 +135,6 @@ const messages = defineMessages({
id: 'auth.sign-in.password.label', id: 'auth.sign-in.password.label',
defaultMessage: 'Password', defaultMessage: 'Password',
}, },
signInButton: {
id: 'auth.sign-in.action.sign-in',
defaultMessage: 'Sign in',
},
signInWithLabel: { signInWithLabel: {
id: 'auth.sign-in.sign-in-with', id: 'auth.sign-in.sign-in-with',
defaultMessage: 'Sign in with', defaultMessage: 'Sign in with',

View File

@@ -115,7 +115,7 @@
<div class="auth-form__additional-options"> <div class="auth-form__additional-options">
{{ formatMessage(messages.alreadyHaveAccountLabel) }} {{ formatMessage(messages.alreadyHaveAccountLabel) }}
<NuxtLink class="text-link" :to="signInLink"> <NuxtLink class="text-link" :to="signInLink">
{{ formatMessage(messages.signInLabel) }} {{ formatMessage(commonMessages.signInButton) }}
</NuxtLink> </NuxtLink>
</div> </div>
</section> </section>
@@ -185,10 +185,6 @@ const messages = defineMessages({
id: 'auth.sign-up.sign-in-option.title', id: 'auth.sign-up.sign-in-option.title',
defaultMessage: 'Already have an account?', defaultMessage: 'Already have an account?',
}, },
signInLabel: {
id: 'auth.sign-up.sign-in-option.action',
defaultMessage: 'Sign in',
},
}) })
useHead({ useHead({

View File

@@ -163,7 +163,7 @@
<div class="input-group"> <div class="input-group">
<button class="iconified-button brand-button" @click="$refs.modal_creation.show()"> <button class="iconified-button brand-button" @click="$refs.modal_creation.show()">
<PlusIcon /> <PlusIcon />
Create a project {{ formatMessage(commonMessages.createAProjectButton) }}
</button> </button>
</div> </div>
</div> </div>
@@ -339,9 +339,11 @@ export default defineNuxtComponent({
DescendingIcon, DescendingIcon,
}, },
async setup() { async setup() {
const { formatMessage } = useVIntl()
const user = await useUser() const user = await useUser()
await initUserProjects() await initUserProjects()
return { user: ref(user) } return { formatMessage, user: ref(user) }
}, },
data() { data() {
return { return {

View File

@@ -163,7 +163,7 @@
<div class="input-group"> <div class="input-group">
<Button color="primary" @click="$refs.modal_creation.show()"> <Button color="primary" @click="$refs.modal_creation.show()">
<PlusIcon /> <PlusIcon />
Create a project {{ formatMessage(commonMessages.createAProjectButton) }}
</Button> </Button>
<OrganizationProjectTransferModal <OrganizationProjectTransferModal
:projects="usersOwnedProjects || []" :projects="usersOwnedProjects || []"
@@ -321,6 +321,8 @@ import {
import ModalCreation from '~/components/ui/ModalCreation.vue' import ModalCreation from '~/components/ui/ModalCreation.vue'
import OrganizationProjectTransferModal from '~/components/ui/OrganizationProjectTransferModal.vue' import OrganizationProjectTransferModal from '~/components/ui/OrganizationProjectTransferModal.vue'
const { formatMessage } = useVIntl()
const { organization, projects, refresh } = inject('organizationContext') const { organization, projects, refresh } = inject('organizationContext')
const auth = await useAuth() const auth = await useAuth()

View File

@@ -15,10 +15,18 @@ export const commonMessages = defineMessages({
id: 'button.continue', id: 'button.continue',
defaultMessage: 'Continue', defaultMessage: 'Continue',
}, },
createAProjectButton: {
id: 'button.create-a-project',
defaultMessage: 'Create a project',
},
createdAgoLabel: { createdAgoLabel: {
id: 'label.created-ago', id: 'label.created-ago',
defaultMessage: 'Created {ago}', defaultMessage: 'Created {ago}',
}, },
dashboardLabel: {
id: 'label.dashboard',
defaultMessage: 'Dashboard',
},
dateAtTimeTooltip: { dateAtTimeTooltip: {
id: 'tooltip.date-at-time', id: 'tooltip.date-at-time',
defaultMessage: '{date, date, long} at {time, time, short}', defaultMessage: '{date, date, long} at {time, time, short}',
@@ -59,6 +67,14 @@ export const commonMessages = defineMessages({
id: 'input.view.list', id: 'input.view.list',
defaultMessage: 'List view', defaultMessage: 'List view',
}, },
moderationLabel: {
id: 'label.moderation',
defaultMessage: 'Moderation',
},
notificationsLabel: {
id: 'label.notifications',
defaultMessage: 'Notifications',
},
privateLabel: { privateLabel: {
id: 'collection.label.private', id: 'collection.label.private',
defaultMessage: 'Private', defaultMessage: 'Private',
@@ -87,6 +103,18 @@ export const commonMessages = defineMessages({
id: 'label.scopes', id: 'label.scopes',
defaultMessage: 'Scopes', defaultMessage: 'Scopes',
}, },
settingsLabel: {
id: 'label.settings',
defaultMessage: 'Settings',
},
signInButton: {
id: 'button.sign-in',
defaultMessage: 'Sign in',
},
signOutButton: {
id: 'button.sign-out',
defaultMessage: 'Sign out',
},
titleLabel: { titleLabel: {
id: 'label.title', id: 'label.title',
defaultMessage: 'Title', defaultMessage: 'Title',