diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index 4088a97322..768b7a64b5 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -81,7 +81,7 @@ jobs: REDIS_CONNECTION_TYPE: multiplexed REDIS_URL: redis://127.0.0.1:7000,redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003,redis://127.0.0.1:7004,redis://127.0.0.1:7005 # Avoid stack overflows in tests - RUST_MIN_STACK: 67108864 + RUST_MIN_STACK: 134217728 steps: - name: Check out code diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 83119f642a..f7e78a4963 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -115,6 +115,7 @@ import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' import { get_opening_command, initialize_state } from '@/helpers/state' import { hasActivePride26Midas, hasMidasBadge } from '@/helpers/user-campaigns.ts' +import { parse_modrinth_user_link } from '@/helpers/users' import { areUpdatesEnabled, enqueueUpdateForInstallation, @@ -265,7 +266,7 @@ providePageContext({ themeStore.getFeatureFlag('server_ram_as_bytes_always_on'), ), }, - openExternalUrl: (url) => openUrl(url), + openExternalUrl: (url) => void openUrl(url), }) provideModalBehavior({ noblur: computed(() => !themeStore.advancedRendering), @@ -966,7 +967,7 @@ async function declineServerInviteNotification(notification) { function openServerInviteInviterProfile(inviterName) { if (!inviterName) return - openUrl(`${config.siteUrl}/user/${encodeURIComponent(inviterName)}`) + void router.push(`/user/${encodeURIComponent(inviterName)}`) } async function handleLiveNotification(notification) { @@ -1407,8 +1408,11 @@ function handleClick(e) { !target.href.startsWith('https://tauri.localhost') && !target.href.startsWith('http://tauri.localhost') ) { + const userPath = parse_modrinth_user_link(target.href) const parsed = parseModrinthLink(target.href) - if (target.target !== '_blank' && parsed) { + if (userPath) { + void router.push(userPath) + } else if (target.target !== '_blank' && parsed) { void openModrinthProjectLinkInApp(parsed) } else { openUrl(target.href) @@ -1654,7 +1658,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload) :options="[ { id: 'view-profile', - action: () => openUrl('https://modrinth.com/user/' + credentials.user.username), + action: () => router.push(`/user/${encodeURIComponent(credentials.user.username)}`), }, { id: 'sign-out', diff --git a/apps/app-frontend/src/components/ui/friends/FriendsSection.vue b/apps/app-frontend/src/components/ui/friends/FriendsSection.vue index 3868c387da..e5bf159b40 100644 --- a/apps/app-frontend/src/components/ui/friends/FriendsSection.vue +++ b/apps/app-frontend/src/components/ui/friends/FriendsSection.vue @@ -8,13 +8,14 @@ import { OverflowMenu, useVIntl, } from '@modrinth/ui' -import { openUrl } from '@tauri-apps/plugin-opener' import { useTemplateRef } from 'vue' +import { useRouter } from 'vue-router' import ContextMenu from '@/components/ui/ContextMenu.vue' import type { FriendWithUserData } from '@/helpers/friends.ts' const { formatMessage } = useVIntl() +const router = useRouter() const props = withDefaults( defineProps<{ @@ -54,7 +55,7 @@ function createContextMenuOptions(friend: FriendWithUserData) { } function openProfile(username: string) { - openUrl('https://modrinth.com/user/' + username) + void router.push(`/user/${encodeURIComponent(username)}`) } const friendOptions = useTemplateRef('friendOptions') diff --git a/apps/app-frontend/src/components/ui/instance-page-header/index.vue b/apps/app-frontend/src/components/ui/instance-page-header/index.vue index 872db1ae5b..1cb84df1ba 100644 --- a/apps/app-frontend/src/components/ui/instance-page-header/index.vue +++ b/apps/app-frontend/src/components/ui/instance-page-header/index.vue @@ -186,8 +186,8 @@ import { type TeleportOverflowMenuItem, useVIntl, } from '@modrinth/ui' -import { openUrl } from '@tauri-apps/plugin-opener' import { computed } from 'vue' +import { useRouter } from 'vue-router' import type { GameInstance } from '@/helpers/types' @@ -247,6 +247,7 @@ const messages = defineMessages({ defaultMessage: "This instance's content is being shared to other users.", }, }) +const router = useRouter() const props = withDefaults( defineProps<{ @@ -331,7 +332,7 @@ const sharedInstanceManagerLabel = computed(() => const sharedInstanceManagerAction = computed(() => { const manager = props.sharedInstanceManager if (manager?.type !== 'user') return undefined - return () => openUrl(`https://modrinth.com/user/${encodeURIComponent(manager.name)}`) + return () => router.push(`/user/${encodeURIComponent(manager.name)}`) }) const playtimeLabel = computed(() => { if (props.timePlayed <= 0) return formatMessage(messages.neverPlayed) diff --git a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-invite-handler/use-shared-instance-invite-handler.ts b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-invite-handler/use-shared-instance-invite-handler.ts index 982420a776..c71fe78f27 100644 --- a/apps/app-frontend/src/components/ui/shared-instances/shared-instance-invite-handler/use-shared-instance-invite-handler.ts +++ b/apps/app-frontend/src/components/ui/shared-instances/shared-instance-invite-handler/use-shared-instance-invite-handler.ts @@ -6,11 +6,9 @@ import { injectPopupNotificationManager, } from '@modrinth/ui' import { useQueryClient } from '@tanstack/vue-query' -import { openUrl } from '@tauri-apps/plugin-opener' import { type Ref, watch } from 'vue' import { useRouter } from 'vue-router' -import { config } from '@/config' import { get_user } from '@/helpers/cache' import { toError } from '@/helpers/errors' import { @@ -216,7 +214,7 @@ export function useSharedInstanceInviteHandler( markNotificationRead(notification).catch((error) => handleError(toError(error))), onOpenActor: () => { if (invite.invitedByUsername) { - openUrl(`${config.siteUrl}/user/${encodeURIComponent(invite.invitedByUsername)}`) + void router.push(`/user/${encodeURIComponent(invite.invitedByUsername)}`) } }, }, diff --git a/apps/app-frontend/src/helpers/users.ts b/apps/app-frontend/src/helpers/users.ts index 042b0d4cbb..800818b4e9 100644 --- a/apps/app-frontend/src/helpers/users.ts +++ b/apps/app-frontend/src/helpers/users.ts @@ -1,11 +1,56 @@ +import type { Labrinth } from '@modrinth/api-client' import { invoke } from '@tauri-apps/api/core' -export type SearchUser = { - id: string - username: string - avatar_url: string | null +// Converts user profile links from rendered Markdown/any dynamic content into app routes. +export function parse_modrinth_user_link(href: string): string | null { + try { + const url = new URL(href) + if (url.hostname !== 'modrinth.com' && url.hostname !== 'www.modrinth.com') return null + + const segments = url.pathname.split('/').filter(Boolean) + if (segments[0]?.toLowerCase() !== 'user' || !segments[1] || segments.length > 3) return null + + const path = `/user/${encodeURIComponent(decodeURIComponent(segments[1]))}` + return segments[2] ? `${path}/${encodeURIComponent(decodeURIComponent(segments[2]))}` : path + } catch { + return null + } } -export async function search_user(query: string): Promise { - return await invoke('plugin:users|search_user', { query }) +export async function search_user(query: string): Promise { + return await invoke('plugin:users|search_user', { query }) +} + +export async function get_user_profile(userId: string): Promise { + return await invoke('plugin:users|get_user_profile', { userId }) +} + +export async function get_user_projects(userId: string): Promise { + return await invoke('plugin:users|get_user_projects', { + userId, + }) +} + +export async function get_user_organizations( + userId: string, +): Promise { + return await invoke( + 'plugin:users|get_user_organizations', + { userId }, + ) +} + +export async function get_user_collections( + userId: string, +): Promise { + return await invoke('plugin:users|get_user_collections', { + userId, + }) +} + +export async function patch_user( + userId: string, + patch: Partial>, +): Promise { + await invoke('plugin:users|patch_user', { userId, patch }) } diff --git a/apps/app-frontend/src/pages/User.vue b/apps/app-frontend/src/pages/User.vue new file mode 100644 index 0000000000..0a5edf77ec --- /dev/null +++ b/apps/app-frontend/src/pages/User.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/app-frontend/src/pages/hosting/manage/Access.vue b/apps/app-frontend/src/pages/hosting/manage/Access.vue index 59e1f8e5cf..d13038152c 100644 --- a/apps/app-frontend/src/pages/hosting/manage/Access.vue +++ b/apps/app-frontend/src/pages/hosting/manage/Access.vue @@ -5,7 +5,6 @@ import { ServersManageAccessPage, } from '@modrinth/ui' import { useQueryClient } from '@tanstack/vue-query' -import { openUrl } from '@tauri-apps/plugin-opener' const client = injectModrinthClient() const { serverId } = injectModrinthServerContext() @@ -29,7 +28,7 @@ try { } function userProfileLink(username: string) { - return () => openUrl(`https://modrinth.com/user/${encodeURIComponent(username)}`) + return `/user/${encodeURIComponent(username)}` } diff --git a/apps/app-frontend/src/pages/index.js b/apps/app-frontend/src/pages/index.js index 5a4294937a..b290622177 100644 --- a/apps/app-frontend/src/pages/index.js +++ b/apps/app-frontend/src/pages/index.js @@ -2,6 +2,7 @@ import Browse from './Browse.vue' import Index from './Index.vue' import Servers from './Servers.vue' import Skins from './Skins.vue' +import User from './User.vue' import Worlds from './Worlds.vue' -export { Browse, Index, Servers, Skins, Worlds } +export { Browse, Index, Servers, Skins, User, Worlds } diff --git a/apps/app-frontend/src/pages/instance/Mods.vue b/apps/app-frontend/src/pages/instance/Mods.vue index 362fb76c60..f7314efbee 100644 --- a/apps/app-frontend/src/pages/instance/Mods.vue +++ b/apps/app-frontend/src/pages/instance/Mods.vue @@ -198,6 +198,13 @@ const messages = defineMessages({ let savedModalState: ModpackContentModalState | null = null +function contentOwnerLink(owner: ContentOwner): NonNullable { + if (owner.type === 'user') return `/user/${encodeURIComponent(owner.id)}` + return () => { + void openUrl(`https://modrinth.com/organization/${owner.id}`) + } +} + const { formatMessage } = useVIntl() const { handleError, addNotification } = injectNotificationManager() const { installingItems, installRevisionByInstance, installFailureRevisionByInstance } = @@ -1390,10 +1397,7 @@ provideContentManager({ owner: linkedModpackOwner.value ? { ...linkedModpackOwner.value, - link: () => - openUrl( - `https://modrinth.com/${linkedModpackOwner.value!.type}/${linkedModpackOwner.value!.id}`, - ), + link: contentOwnerLink(linkedModpackOwner.value), } : undefined, categories: linkedModpackCategories.value, @@ -1491,7 +1495,7 @@ provideContentManager({ owner: item.owner ? { ...item.owner, - link: () => openUrl(`https://modrinth.com/${item.owner!.type}/${item.owner!.id}`), + link: contentOwnerLink(item.owner), } : undefined, enabled: canMutateContent(item) ? item.enabled : undefined, diff --git a/apps/app-frontend/src/pages/instance/share/index.vue b/apps/app-frontend/src/pages/instance/share/index.vue index 65b0f96910..10ba2b04e0 100644 --- a/apps/app-frontend/src/pages/instance/share/index.vue +++ b/apps/app-frontend/src/pages/instance/share/index.vue @@ -138,7 +138,6 @@ import { useVIntl, } from '@modrinth/ui' import { useQueryClient } from '@tanstack/vue-query' -import { openUrl } from '@tauri-apps/plugin-opener' import { computed, ref, toRef, watch } from 'vue' import ModrinthAccountRequiredModal from '@/components/ui/modal/ModrinthAccountRequiredModal.vue' @@ -342,9 +341,7 @@ function removeMember(row: ShareRow) { members.remove(row.id) } function userProfileLink(username: string) { - return !username || username.includes('@') - ? undefined - : () => openUrl(`https://modrinth.com/user/${encodeURIComponent(username)}`) + return !username || username.includes('@') ? undefined : `/user/${encodeURIComponent(username)}` } async function requestAuth(flow: ModrinthAuthFlow) { await auth.requestSignIn(`/instance/${encodeURIComponent(props.instance.id)}/share`, flow, { diff --git a/apps/app-frontend/src/pages/instance/share/shared-instance-members-table.vue b/apps/app-frontend/src/pages/instance/share/shared-instance-members-table.vue index 1dc637e21c..85abb14955 100644 --- a/apps/app-frontend/src/pages/instance/share/shared-instance-members-table.vue +++ b/apps/app-frontend/src/pages/instance/share/shared-instance-members-table.vue @@ -157,7 +157,6 @@ import { useRelativeTime, useVIntl, } from '@modrinth/ui' -import { openUrl } from '@tauri-apps/plugin-opener' import { computed, ref } from 'vue' import { @@ -294,9 +293,7 @@ const messages = defineMessages({ }, }) function userProfileLink(username: string) { - return !username || username.includes('@') - ? undefined - : () => openUrl(`https://modrinth.com/user/${encodeURIComponent(username)}`) + return !username || username.includes('@') ? undefined : `/user/${encodeURIComponent(username)}` } function setUsernameRef(id: string, element: Element | null) { usernameRefs.value[id] = element instanceof HTMLElement ? element : null diff --git a/apps/app-frontend/src/pages/project/Index.vue b/apps/app-frontend/src/pages/project/Index.vue index e09914fcc6..5dd0476df6 100644 --- a/apps/app-frontend/src/pages/project/Index.vue +++ b/apps/app-frontend/src/pages/project/Index.vue @@ -31,8 +31,9 @@ :organization="organization" :members="members" :org-link="(slug) => `https://modrinth.com/organization/${slug}`" - :user-link="(username) => `https://modrinth.com/user/${username}`" + :user-link="(username) => `/user/${encodeURIComponent(username)}`" link-target="_blank" + :user-link-target="null" class="project-sidebar-section" /> openUrl(`https://modrinth.com/user/${owner.user.username}`), + link: `/user/${encodeURIComponent(owner.user.username)}`, }, } } diff --git a/apps/app-frontend/src/providers/setup/server-install-content.ts b/apps/app-frontend/src/providers/setup/server-install-content.ts index 9875173dad..c48b90f2c1 100644 --- a/apps/app-frontend/src/providers/setup/server-install-content.ts +++ b/apps/app-frontend/src/providers/setup/server-install-content.ts @@ -108,7 +108,7 @@ function getQueuedInstallOwnerFallback(project: InstallableSearchResult) { id: ownerId, name: project.author, type: 'user' as const, - link: `https://modrinth.com/user/${ownerId}`, + link: `/user/${encodeURIComponent(ownerId)}`, } } @@ -144,7 +144,7 @@ async function getQueuedInstallOwner( name: owner.username, type: 'user' as const, avatar_url: owner.avatar_url, - link: `https://modrinth.com/user/${owner.username}`, + link: `/user/${encodeURIComponent(owner.username)}`, } } } catch { diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index 2baa1edd06..af0131c4be 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -100,6 +100,14 @@ export default new createRouter({ breadcrumb: [{ name: 'Skin selector' }], }, }, + { + path: '/user/:user/:projectType?', + name: 'User', + component: Pages.User, + meta: { + breadcrumb: [{ name: '?User' }], + }, + }, { path: '/library', name: 'Library', diff --git a/apps/app/build.rs b/apps/app/build.rs index c7fe2d490b..eb826b13da 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -276,7 +276,14 @@ fn main() { .plugin( "users", InlinedPlugin::new() - .commands(&["search_user"]) + .commands(&[ + "search_user", + "get_user_profile", + "get_user_projects", + "get_user_organizations", + "get_user_collections", + "patch_user", + ]) .default_permission( DefaultPermissionRule::AllowAllCommands, ), diff --git a/apps/app/src/api/users.rs b/apps/app/src/api/users.rs index b4c5dba75a..8160ce7642 100644 --- a/apps/app/src/api/users.rs +++ b/apps/app/src/api/users.rs @@ -1,4 +1,5 @@ use crate::api::Result; +use serde_json::Value; use theseus::users::SearchUser; #[tauri::command] @@ -6,8 +7,40 @@ pub async fn search_user(query: &str) -> Result> { Ok(theseus::users::search_user(query).await?) } +#[tauri::command] +pub async fn get_user_profile(user_id: &str) -> Result { + Ok(theseus::users::get_user_profile(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_projects(user_id: &str) -> Result { + Ok(theseus::users::get_user_projects(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_organizations(user_id: &str) -> Result { + Ok(theseus::users::get_user_organizations(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_collections(user_id: &str) -> Result { + Ok(theseus::users::get_user_collections(user_id).await?) +} + +#[tauri::command] +pub async fn patch_user(user_id: &str, patch: Value) -> Result<()> { + Ok(theseus::users::patch_user(user_id, patch).await?) +} + pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("users") - .invoke_handler(tauri::generate_handler![search_user]) + .invoke_handler(tauri::generate_handler![ + search_user, + get_user_profile, + get_user_projects, + get_user_organizations, + get_user_collections, + patch_user, + ]) .build() } diff --git a/apps/frontend/src/locales/ar-SA/index.json b/apps/frontend/src/locales/ar-SA/index.json index 9a4bfda0d4..88e9f816b7 100644 --- a/apps/frontend/src/locales/ar-SA/index.json +++ b/apps/frontend/src/locales/ar-SA/index.json @@ -1838,9 +1838,6 @@ "muralpay.placeholder.cuit-cuil": { "message": "أدخل CUIT أو CUIL" }, - "profile.user-id": { - "message": "معرّف المستخدم: {id}" - }, "project-member-header.success-decline": { "message": "لقد رفضت دعوة الفريق" }, diff --git a/apps/frontend/src/locales/cs-CZ/index.json b/apps/frontend/src/locales/cs-CZ/index.json index 2001cd3669..f208d9f3dd 100644 --- a/apps/frontend/src/locales/cs-CZ/index.json +++ b/apps/frontend/src/locales/cs-CZ/index.json @@ -2105,111 +2105,6 @@ "muralpay.placeholder.account-number": { "message": "Zadejte číslo účtu" }, - "profile.bio.fallback.creator": { - "message": "Modrinth tvůrce." - }, - "profile.bio.fallback.user": { - "message": "Modrinth uživatel." - }, - "profile.button.analytics": { - "message": "Zobrazit analytika uživatele" - }, - "profile.button.billing": { - "message": "Spravujte fakturaci uživatele" - }, - "profile.button.edit-role": { - "message": "Upravit roli" - }, - "profile.button.info": { - "message": "Podívejte se na detaily uživatele" - }, - "profile.button.manage-projects": { - "message": "Spravujte projekty" - }, - "profile.button.remove-affiliate": { - "message": "Odebrat jako partnera" - }, - "profile.button.set-affiliate": { - "message": "Nastavit jako partnera" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# projekt} few {# projekty} other {# projektů}}" - }, - "profile.details.label.auth-providers": { - "message": "Poskytovatelé přihlášení" - }, - "profile.details.label.email": { - "message": "E-mail" - }, - "profile.details.label.email-verified": { - "message": "E-mail ověřen" - }, - "profile.details.label.has-password": { - "message": "Má heslo" - }, - "profile.details.label.has-totp": { - "message": "Má TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Platební metody" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-mail není ověřen" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mail je ověřen" - }, - "profile.error.not-found": { - "message": "Uživatel nenalezen" - }, - "profile.label.affiliate": { - "message": "Partner" - }, - "profile.label.badges": { - "message": "Odznaky" - }, - "profile.label.collection": { - "message": "Kolekce" - }, - "profile.label.joined": { - "message": "Členem od" - }, - "profile.label.no-collections": { - "message": "Tento uživatel nemá žádné kolekce!" - }, - "profile.label.no-collections-auth": { - "message": "Nemáte žádné kolekce.\nChcete nějakou vytvořit?" - }, - "profile.label.no-projects": { - "message": "Tento uživatel nemá žádné projekty!" - }, - "profile.label.no-projects-auth": { - "message": "Nemáte žádné projekty.\nChcete nějaký vytvořit?" - }, - "profile.label.organizations": { - "message": "Organizace" - }, - "profile.label.saving": { - "message": "Ukládání..." - }, - "profile.meta.description": { - "message": "Stáhněte si projekty od {username} na Modrinthu" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Stáhněte si projekty od {username} na Modrinthu" - }, - "profile.official-account": { - "message": "Oficiální Modrinth účet" - }, - "profile.official-account.bio": { - "message": "Oficiální uživatelský účet Modrinthu. Podporu získáte na nebo e-mailem na " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} sledující projektu} few {{count} sledující projektu} other {{count} sledujících projektu}}" - }, - "profile.user-id": { - "message": "ID uživatele: {id}" - }, "project-member-header.error-decline": { "message": "Nepodařilo se zamítnout pozvánku do týmu" }, diff --git a/apps/frontend/src/locales/da-DK/index.json b/apps/frontend/src/locales/da-DK/index.json index d474700856..47cc387494 100644 --- a/apps/frontend/src/locales/da-DK/index.json +++ b/apps/frontend/src/locales/da-DK/index.json @@ -2180,87 +2180,6 @@ "organization.projects.none-with-create-prompt": { "message": "Denne organisation har ikke nogle projekter endnu. Kunnde dig tænke dig at oprette et?" }, - "profile.bio.fallback.creator": { - "message": "En Modrinth skaber." - }, - "profile.bio.fallback.user": { - "message": "En Modrinth bruger." - }, - "profile.button.edit-role": { - "message": "Rediger rolle" - }, - "profile.button.info": { - "message": "Vis bruger detaljer" - }, - "profile.button.manage-projects": { - "message": "Administrer projekter" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Email bekræftet" - }, - "profile.details.label.has-password": { - "message": "Har adgangskode" - }, - "profile.details.label.has-totp": { - "message": "Har TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Betalingsmetoder" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Email ikke bekræftet" - }, - "profile.details.tooltip.email-verified": { - "message": "Email bekræftet" - }, - "profile.error.not-found": { - "message": "Bruger ikke fundet" - }, - "profile.label.badges": { - "message": "Emblemer" - }, - "profile.label.collection": { - "message": "Samling" - }, - "profile.label.download-count": { - "message": "{count, plural, one {download} other {downloads}}" - }, - "profile.label.joined": { - "message": "Tilsluttet" - }, - "profile.label.no-collections": { - "message": "Denne bruger har ingen samlinger!" - }, - "profile.label.no-collections-auth": { - "message": "Du har ingen samlinger.\nKunne du tænke dig at oprette en?" - }, - "profile.label.no-projects": { - "message": "Denne bruger har ingen projekter!" - }, - "profile.label.no-projects-auth": { - "message": "Du har ingen projekter.\nKunne du tænke dig at oprette en?" - }, - "profile.label.organizations": { - "message": "Organisationer" - }, - "profile.label.saving": { - "message": "Gemmer..." - }, - "profile.meta.description": { - "message": "Download {username}'s projekter på Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Download {username}'s projekter på Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} projekt følger} other {{count} projekt følgere}}" - }, - "profile.user-id": { - "message": "Bruger-ID: {id}" - }, "project-member-header.error-decline": { "message": "Afvisning af hold invitation fejlede" }, diff --git a/apps/frontend/src/locales/de-CH/index.json b/apps/frontend/src/locales/de-CH/index.json index aaca39829d..82abb731bd 100644 --- a/apps/frontend/src/locales/de-CH/index.json +++ b/apps/frontend/src/locales/de-CH/index.json @@ -3320,117 +3320,6 @@ "organization.projects.none-with-create-prompt": { "message": "Diese Organisation hat noch keine Projekte. Möchtest du eins erstellen?" }, - "profile.bio.fallback.creator": { - "message": "Ein Modrinth Ersteller." - }, - "profile.bio.fallback.user": { - "message": "Ein Modrinth Benutzer." - }, - "profile.button.analytics": { - "message": "Nutzungsstatistik anzeigen" - }, - "profile.button.billing": { - "message": "Nutzer Zahlungen verwalten" - }, - "profile.button.edit-role": { - "message": "Rolle bearbeiten" - }, - "profile.button.info": { - "message": "Nutzerdetails ansehen" - }, - "profile.button.manage-projects": { - "message": "Projekte verwalten" - }, - "profile.button.remove-affiliate": { - "message": "Als Partner entfernen" - }, - "profile.button.set-affiliate": { - "message": "Als Partner setzen" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# Projekt} other {# Projekte}}" - }, - "profile.details.label.auth-providers": { - "message": "Authentifizierungsanbieter" - }, - "profile.details.label.email": { - "message": "E-Mail" - }, - "profile.details.label.email-verified": { - "message": "E-Mail verifiziert" - }, - "profile.details.label.has-password": { - "message": "Hat Passwort" - }, - "profile.details.label.has-totp": { - "message": "Hat TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Zahlungsmethoden" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-Mail nicht verifiziert" - }, - "profile.details.tooltip.email-verified": { - "message": "E-Mail verifiziert" - }, - "profile.error.not-found": { - "message": "Benutzer nicht gefunden" - }, - "profile.label.affiliate": { - "message": "Partner" - }, - "profile.label.badges": { - "message": "Abzeichen" - }, - "profile.label.collection": { - "message": "Kollektion" - }, - "profile.label.download-count": { - "message": "{count, plural, one {Download} other {Downloads}}" - }, - "profile.label.joined": { - "message": "Beigetreten" - }, - "profile.label.no-collections": { - "message": "Dieser Nutzer hat keine Kollektionen!" - }, - "profile.label.no-collections-auth": { - "message": "Du hast keine Kollektionen.\nMöchtest du gerne eine erstellen?" - }, - "profile.label.no-projects": { - "message": "Dieser Nutzer hat keine Projekte!" - }, - "profile.label.no-projects-auth": { - "message": "Du hast keine Projekte.\nMöchtest du gerne eines erstellen?" - }, - "profile.label.organizations": { - "message": "Organisationen" - }, - "profile.label.project-count": { - "message": "{count, plural, one {Projekt} other {Projekte}}" - }, - "profile.label.saving": { - "message": "Speichert..." - }, - "profile.meta.description": { - "message": "Lade {username}'s Projekte auf Modrinth herunter" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Lade {username}'s Projekte auf Modrinth herunter" - }, - "profile.official-account": { - "message": "Offizielles Modrinth-Konto" - }, - "profile.official-account.bio": { - "message": "Das offizielle Benutzerkonto von Modrinth. Erhalte Support unter oder per E-Mail an " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count}} other {{count}}} Projekt-Follower" - }, - "profile.user-id": { - "message": "Benutzer ID: {id}" - }, "project-member-header.error-decline": { "message": "Ablehnen der Team-Einladung fehlgeschlagen" }, diff --git a/apps/frontend/src/locales/de-DE/index.json b/apps/frontend/src/locales/de-DE/index.json index 07380c6b42..2e8c9d661b 100644 --- a/apps/frontend/src/locales/de-DE/index.json +++ b/apps/frontend/src/locales/de-DE/index.json @@ -3320,117 +3320,6 @@ "organization.projects.none-with-create-prompt": { "message": "Diese Organisation hat noch keine Projekte. Möchtest du eins erstellen?" }, - "profile.bio.fallback.creator": { - "message": "Ein Modrinth-Ersteller." - }, - "profile.bio.fallback.user": { - "message": "Ein Modrinth-Benutzer." - }, - "profile.button.analytics": { - "message": "Benutzeranalysen anzeigen" - }, - "profile.button.billing": { - "message": "Benutzerabrechnungen verwalten" - }, - "profile.button.edit-role": { - "message": "Rolle bearbeiten" - }, - "profile.button.info": { - "message": "Nutzerdetails ansehen" - }, - "profile.button.manage-projects": { - "message": "Projekte verwalten" - }, - "profile.button.remove-affiliate": { - "message": "Als Partner entfernen" - }, - "profile.button.set-affiliate": { - "message": "Als Partner setzen" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# Projekt} other {# Projekte}}" - }, - "profile.details.label.auth-providers": { - "message": "Authentifizierungsanbieter" - }, - "profile.details.label.email": { - "message": "E-Mail" - }, - "profile.details.label.email-verified": { - "message": "E-Mail verifiziert" - }, - "profile.details.label.has-password": { - "message": "Hat Passwort" - }, - "profile.details.label.has-totp": { - "message": "Hat TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Zahlungsmethoden" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-Mail nicht verifiziert" - }, - "profile.details.tooltip.email-verified": { - "message": "E-Mail verifiziert" - }, - "profile.error.not-found": { - "message": "Benutzer nicht gefunden" - }, - "profile.label.affiliate": { - "message": "Partner" - }, - "profile.label.badges": { - "message": "Abzeichen" - }, - "profile.label.collection": { - "message": "Kollektion" - }, - "profile.label.download-count": { - "message": "{count, plural, one {Download} other {Downloads}}" - }, - "profile.label.joined": { - "message": "Beigetreten" - }, - "profile.label.no-collections": { - "message": "Dieser Nutzer hat keine Kollektionen!" - }, - "profile.label.no-collections-auth": { - "message": "Du hast keine Kollektionen.\nMöchtest du eine erstellen?" - }, - "profile.label.no-projects": { - "message": "Dieser Nutzer hat keine Projekte!" - }, - "profile.label.no-projects-auth": { - "message": "Du hast keine Projekte.\nMöchtest du eins erstellen?" - }, - "profile.label.organizations": { - "message": "Organisationen" - }, - "profile.label.project-count": { - "message": "{count, plural, one {Projekt} other {Projekte}}" - }, - "profile.label.saving": { - "message": "Speichert..." - }, - "profile.meta.description": { - "message": "Lade Projekte von {username} auf Modrinth herunter" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Lade Projekte von {username} auf Modrinth herunter" - }, - "profile.official-account": { - "message": "Offizielles Konto von Modrinth" - }, - "profile.official-account.bio": { - "message": "Das offizielle Benutzerkonto von Modrinth. Erhalte Hilfe unter oder per E-Mail unter " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count}} other {{count}}} Projekt-Follower" - }, - "profile.user-id": { - "message": "Benutzer ID: {id}" - }, "project-member-header.error-decline": { "message": "Teameinladung konnte nicht abgelehnt werden" }, diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index 9572b5ce7f..5946c85cdd 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -3344,117 +3344,6 @@ "organization.projects.none-with-create-prompt": { "message": "This organization doesn't have any projects yet. Would you like to create one?" }, - "profile.bio.fallback.creator": { - "message": "A Modrinth creator." - }, - "profile.bio.fallback.user": { - "message": "A Modrinth user." - }, - "profile.button.analytics": { - "message": "View user analytics" - }, - "profile.button.billing": { - "message": "Manage user billing" - }, - "profile.button.edit-role": { - "message": "Edit role" - }, - "profile.button.info": { - "message": "View user details" - }, - "profile.button.manage-projects": { - "message": "Manage projects" - }, - "profile.button.remove-affiliate": { - "message": "Remove as affiliate" - }, - "profile.button.set-affiliate": { - "message": "Set as affiliate" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# project} other {# projects}}" - }, - "profile.details.label.auth-providers": { - "message": "Auth providers" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Email verified" - }, - "profile.details.label.has-password": { - "message": "Has password" - }, - "profile.details.label.has-totp": { - "message": "Has TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Payment methods" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Email not verified" - }, - "profile.details.tooltip.email-verified": { - "message": "Email verified" - }, - "profile.error.not-found": { - "message": "User not found" - }, - "profile.label.affiliate": { - "message": "Affiliate" - }, - "profile.label.badges": { - "message": "Badges" - }, - "profile.label.collection": { - "message": "Collection" - }, - "profile.label.download-count": { - "message": "{count, plural, one {download} other {downloads}}" - }, - "profile.label.joined": { - "message": "Joined" - }, - "profile.label.no-collections": { - "message": "This user has no collections!" - }, - "profile.label.no-collections-auth": { - "message": "You don't have any collections.\nWould you like to create one?" - }, - "profile.label.no-projects": { - "message": "This user has no projects!" - }, - "profile.label.no-projects-auth": { - "message": "You don't have any projects.\nWould you like to create one?" - }, - "profile.label.organizations": { - "message": "Organizations" - }, - "profile.label.project-count": { - "message": "{count, plural, one {project} other {projects}}" - }, - "profile.label.saving": { - "message": "Saving..." - }, - "profile.meta.description": { - "message": "Download {username}'s projects on Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Download {username}'s projects on Modrinth" - }, - "profile.official-account": { - "message": "Official Modrinth account" - }, - "profile.official-account.bio": { - "message": "The official user account of Modrinth. Get support at or via email at " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} project follower} other {{count} project followers}}" - }, - "profile.user-id": { - "message": "User ID: {id}" - }, "project-member-header.error-decline": { "message": "Failed to decline team invitation" }, diff --git a/apps/frontend/src/locales/es-419/index.json b/apps/frontend/src/locales/es-419/index.json index 4f6ffc627f..9cc158700b 100644 --- a/apps/frontend/src/locales/es-419/index.json +++ b/apps/frontend/src/locales/es-419/index.json @@ -2996,111 +2996,6 @@ "muralpay.warning.wallet-address": { "message": "Verifica dos veces la dirección de tu billetera. Los fondos enviados a una dirección incorrecta no se pueden recuperar." }, - "profile.bio.fallback.creator": { - "message": "Un creador de Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Un usuario de Modrinth." - }, - "profile.button.analytics": { - "message": "Ver estadísticas de usuario" - }, - "profile.button.billing": { - "message": "Gestionar la facturación de usuario" - }, - "profile.button.edit-role": { - "message": "Editar rol" - }, - "profile.button.info": { - "message": "Ver detalles de usuario" - }, - "profile.button.manage-projects": { - "message": "Gestionar proyectos" - }, - "profile.button.remove-affiliate": { - "message": "Remover como afiliado" - }, - "profile.button.set-affiliate": { - "message": "Configurar como afiliado" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# proyecto} other {# proyectos}}" - }, - "profile.details.label.auth-providers": { - "message": "Proveedores de autenticación" - }, - "profile.details.label.email": { - "message": "Correo electrónico" - }, - "profile.details.label.email-verified": { - "message": "Correo electrónico verificado" - }, - "profile.details.label.has-password": { - "message": "Tiene contraseña" - }, - "profile.details.label.has-totp": { - "message": "Tiene TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Métodos de pago" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Correo electrónico no verificado" - }, - "profile.details.tooltip.email-verified": { - "message": "Correo electrónico verificado" - }, - "profile.error.not-found": { - "message": "Usuario no encontrado" - }, - "profile.label.affiliate": { - "message": "Afiliado" - }, - "profile.label.badges": { - "message": "Insignias" - }, - "profile.label.collection": { - "message": "Colección" - }, - "profile.label.joined": { - "message": "Se unió" - }, - "profile.label.no-collections": { - "message": "¡Este usuario no tiene colecciones!" - }, - "profile.label.no-collections-auth": { - "message": "No tienes ninguna colección.\n¿Te gustaría crear una?" - }, - "profile.label.no-projects": { - "message": "¡Este usuario no tiene proyectos!" - }, - "profile.label.no-projects-auth": { - "message": "No tienes ningún proyecto.\n¿Te gustaría crear uno?" - }, - "profile.label.organizations": { - "message": "Organizaciones" - }, - "profile.label.saving": { - "message": "Guardando..." - }, - "profile.meta.description": { - "message": "Descarga proyectos de {username} en Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Descarga proyectos de {username} en Modrinth" - }, - "profile.official-account": { - "message": "Cuenta oficial de Modrinth" - }, - "profile.official-account.bio": { - "message": "La cuenta de usuario oficial de Modrinth. Obtén soporte en o vía correo en " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} seguidor del proyecto} other {{count} seguidores del proyecto}}" - }, - "profile.user-id": { - "message": "ID de usuario: {id}" - }, "project-member-header.error-decline": { "message": "Error al rechazar la invitación del equipo" }, diff --git a/apps/frontend/src/locales/es-ES/index.json b/apps/frontend/src/locales/es-ES/index.json index dee3968a7e..53275ab0e6 100644 --- a/apps/frontend/src/locales/es-ES/index.json +++ b/apps/frontend/src/locales/es-ES/index.json @@ -2822,111 +2822,6 @@ "muralpay.warning.wallet-address": { "message": "Verifique la dirección de su billetera. Los fondos enviados a una dirección incorrecta no se pueden recuperar." }, - "profile.bio.fallback.creator": { - "message": "Creador en Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Usuario de Modrinth." - }, - "profile.button.analytics": { - "message": "Ver analíticas de usuario" - }, - "profile.button.billing": { - "message": "Gestionar la facturación de los usuarios" - }, - "profile.button.edit-role": { - "message": "Editar rol" - }, - "profile.button.info": { - "message": "Ver detalles del usuario" - }, - "profile.button.manage-projects": { - "message": "Gestionar proyectos" - }, - "profile.button.remove-affiliate": { - "message": "Borrar como afiliado" - }, - "profile.button.set-affiliate": { - "message": "Establecer como afiliado" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# proyecto} other {# proyectos}}" - }, - "profile.details.label.auth-providers": { - "message": "Autentificar proveedores" - }, - "profile.details.label.email": { - "message": "Correo electrónico" - }, - "profile.details.label.email-verified": { - "message": "Email verificado" - }, - "profile.details.label.has-password": { - "message": "Tiene contraseña" - }, - "profile.details.label.has-totp": { - "message": "Tiene autentificación de doble factor" - }, - "profile.details.label.payment-methods": { - "message": "Métodos de pago" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Correo electrónico no verificado" - }, - "profile.details.tooltip.email-verified": { - "message": "Correo electrónico verificado" - }, - "profile.error.not-found": { - "message": "Usuario no encontrado" - }, - "profile.label.affiliate": { - "message": "Afiliado" - }, - "profile.label.badges": { - "message": "Medallas" - }, - "profile.label.collection": { - "message": "Colección" - }, - "profile.label.joined": { - "message": "Se unió hace" - }, - "profile.label.no-collections": { - "message": "¡Este usuario no tiene colecciones!" - }, - "profile.label.no-collections-auth": { - "message": "No tienes ninguna colección.\n¿Te gustaría crear una?" - }, - "profile.label.no-projects": { - "message": "¡Este usuario no tiene proyectos!" - }, - "profile.label.no-projects-auth": { - "message": "No tienes ningún proyecto.\n¿Te gustaría crear uno?" - }, - "profile.label.organizations": { - "message": "Organizaciones" - }, - "profile.label.saving": { - "message": "Guardando..." - }, - "profile.meta.description": { - "message": "Descargar los proyectos de {username} en Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Descarga los proyectos en Modrinth de {username}" - }, - "profile.official-account": { - "message": "Cuenta oficial de Modrinth" - }, - "profile.official-account.bio": { - "message": "La cuenta de usuario oficial de Modrinth. Obtén soporte en o vía correo en " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} seguidor} other {{count} seguidores}}" - }, - "profile.user-id": { - "message": "ID de usuario: {id}" - }, "project-member-header.error-decline": { "message": "No se ha podido rechazar la invitación del equipo" }, diff --git a/apps/frontend/src/locales/fi-FI/index.json b/apps/frontend/src/locales/fi-FI/index.json index 4d47059f86..8aadcd8f79 100644 --- a/apps/frontend/src/locales/fi-FI/index.json +++ b/apps/frontend/src/locales/fi-FI/index.json @@ -908,15 +908,6 @@ "moderation.page.reports": { "message": "Raportit" }, - "profile.label.badges": { - "message": "Arvomerkit" - }, - "profile.label.organizations": { - "message": "Organisaatiot" - }, - "profile.user-id": { - "message": "Käyttäjätunnus: {id}" - }, "project-moderation-nags.required": { "message": "Vaadittu" }, diff --git a/apps/frontend/src/locales/fil-PH/index.json b/apps/frontend/src/locales/fil-PH/index.json index daf3840759..1f5a8c852a 100644 --- a/apps/frontend/src/locales/fil-PH/index.json +++ b/apps/frontend/src/locales/fil-PH/index.json @@ -2153,99 +2153,6 @@ "muralpay.warning.wallet-address": { "message": "Pakisuri muli ng iyong wallet address. Ang mga pundong nahatid sa maling address ay hindi na mababawi." }, - "profile.bio.fallback.creator": { - "message": "Isang tagagawa sa Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Isang tagagamit sa Modrinth." - }, - "profile.button.billing": { - "message": "Pamahalaan ang billing ng tagagamit" - }, - "profile.button.edit-role": { - "message": "Baguhin ang tungkulin" - }, - "profile.button.info": { - "message": "Tingang ang mga detalye ng tagagamit" - }, - "profile.button.manage-projects": { - "message": "Pamahalaan ang mga proyekto" - }, - "profile.button.remove-affiliate": { - "message": "Tanggalin bilang affiliate" - }, - "profile.button.set-affiliate": { - "message": "Itakda bilang affiliate" - }, - "profile.details.label.auth-providers": { - "message": "Mga auth provider" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Napatunayan na email" - }, - "profile.details.label.has-password": { - "message": "May password" - }, - "profile.details.label.has-totp": { - "message": "May TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Mga paraan ng pagbabayad" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Hindi napatunayang email" - }, - "profile.details.tooltip.email-verified": { - "message": "Napatunayan na email" - }, - "profile.error.not-found": { - "message": "Hindi mahanap na tagagamit" - }, - "profile.label.affiliate": { - "message": "Affiliate" - }, - "profile.label.badges": { - "message": "Tsapa" - }, - "profile.label.collection": { - "message": "Koleksiyon" - }, - "profile.label.joined": { - "message": "Sumali" - }, - "profile.label.no-collections": { - "message": "Walang mga koleksiyon ang tagagamit na ito!" - }, - "profile.label.no-collections-auth": { - "message": "Wala kang koleksiyon.\nNais mo bang lumikha ng isa?" - }, - "profile.label.no-projects": { - "message": "Walang mga proyekto ang tagagmit na ito!" - }, - "profile.label.no-projects-auth": { - "message": "Wala kang proyekto.\nNais mo bang lumikha ng isa?" - }, - "profile.label.organizations": { - "message": "Mga organisasyon" - }, - "profile.label.saving": { - "message": "Sine-save..." - }, - "profile.meta.description": { - "message": "Magdownload ng mga proyekto ni {username} sa Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Magdownload ng mga proyekto ni {username} sa Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} follower ng proyekto} other {{count} na follower ng proyekto}}" - }, - "profile.user-id": { - "message": "User ID: {id}" - }, "project-member-header.error-decline": { "message": "Bigong matanggihan ang anyaya sa koponan" }, diff --git a/apps/frontend/src/locales/fr-FR/index.json b/apps/frontend/src/locales/fr-FR/index.json index d4dd8324d3..cb5e7482f4 100644 --- a/apps/frontend/src/locales/fr-FR/index.json +++ b/apps/frontend/src/locales/fr-FR/index.json @@ -3323,117 +3323,6 @@ "organization.projects.none-with-create-prompt": { "message": "Cette organisation n'a pas encore de projet. Souhaitez-vous en créer un ?" }, - "profile.bio.fallback.creator": { - "message": "Un créateur Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Un utilisateur Modrinth." - }, - "profile.button.analytics": { - "message": "Voir les analyses de l'utilisateur" - }, - "profile.button.billing": { - "message": "Gérer la facturation de l’utilisateur" - }, - "profile.button.edit-role": { - "message": "Modifier le rôle" - }, - "profile.button.info": { - "message": "Afficher les détails de l’utilisateur" - }, - "profile.button.manage-projects": { - "message": "Gérer les projets" - }, - "profile.button.remove-affiliate": { - "message": "Se Désaffilié" - }, - "profile.button.set-affiliate": { - "message": "Se mettre comme affilié" - }, - "profile.collection.projects-count": { - "message": "{count, plural,one {# projet} other {# projets}}" - }, - "profile.details.label.auth-providers": { - "message": "Fournisseurs d’authentification" - }, - "profile.details.label.email": { - "message": "Adresse e-mail" - }, - "profile.details.label.email-verified": { - "message": "Adresse e-mail vérifiée" - }, - "profile.details.label.has-password": { - "message": "Dispose d'un mot de passe" - }, - "profile.details.label.has-totp": { - "message": "Dispose d'un TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Moyens de paiements" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Adresse e-mail non vérifiée" - }, - "profile.details.tooltip.email-verified": { - "message": "Adresse e-mail vérifiée" - }, - "profile.error.not-found": { - "message": "Utilisateur introuvable" - }, - "profile.label.affiliate": { - "message": "Affilié" - }, - "profile.label.badges": { - "message": "Badges" - }, - "profile.label.collection": { - "message": "Collection" - }, - "profile.label.download-count": { - "message": "{count, plural, one {téléchargement} other {téléchargements}}" - }, - "profile.label.joined": { - "message": "Rejoint" - }, - "profile.label.no-collections": { - "message": "Cet utilisateur n'a aucune collection !" - }, - "profile.label.no-collections-auth": { - "message": "Vous n'avez aucune collection.\nVoulez-vous en créer une ?" - }, - "profile.label.no-projects": { - "message": "Cet utilisateur n'a aucun projet !" - }, - "profile.label.no-projects-auth": { - "message": "Vous n'avez aucun projet.\nVoulez-vous en créer un ?" - }, - "profile.label.organizations": { - "message": "Organisations" - }, - "profile.label.project-count": { - "message": "{count, plural, one {projet} other {projets}}" - }, - "profile.label.saving": { - "message": "Sauvegarde en cours..." - }, - "profile.meta.description": { - "message": "Téléchargez les projets de {username} sur Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Téléchargez les projets de {username} sur Modrinth" - }, - "profile.official-account": { - "message": "Compte Modrinth Officiel" - }, - "profile.official-account.bio": { - "message": "Le compte utilisateur officiel de Modrinth. Obtenez de l’aide via ou par e-mail à " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} personne adore} other {{count} personnes adorent}}" - }, - "profile.user-id": { - "message": "ID utilisateur : {id}" - }, "project-member-header.error-decline": { "message": "Échec du refus de l’invitation à l’équipe" }, diff --git a/apps/frontend/src/locales/he-IL/index.json b/apps/frontend/src/locales/he-IL/index.json index 230f506a0d..c538edc714 100644 --- a/apps/frontend/src/locales/he-IL/index.json +++ b/apps/frontend/src/locales/he-IL/index.json @@ -1778,99 +1778,6 @@ "muralpay.warning.wallet-address": { "message": "בדוק היטב את כתובת הארנק שלך. לא ניתן לשחזר כספים שנשלחו לכתובת שגויה." }, - "profile.bio.fallback.creator": { - "message": "יוצר ב-Modrinth." - }, - "profile.bio.fallback.user": { - "message": "משתמש ב-Modrinth." - }, - "profile.button.billing": { - "message": "נהל את חיוב המשתמש" - }, - "profile.button.edit-role": { - "message": "עריכת תפקיד" - }, - "profile.button.info": { - "message": "הצג את הפרטים של המשתמש" - }, - "profile.button.manage-projects": { - "message": "ניהול פרויקטים" - }, - "profile.button.remove-affiliate": { - "message": "הסרה מתוכנית השותפים" - }, - "profile.button.set-affiliate": { - "message": "הגדר כשותף" - }, - "profile.details.label.auth-providers": { - "message": "ספקי אימות זהות" - }, - "profile.details.label.email": { - "message": "אימייל" - }, - "profile.details.label.email-verified": { - "message": "כתובת האימייל אומתה" - }, - "profile.details.label.has-password": { - "message": "יש סיסמה" - }, - "profile.details.label.has-totp": { - "message": "אימות דו-שלבי פעיל" - }, - "profile.details.label.payment-methods": { - "message": "שיטת תשלום" - }, - "profile.details.tooltip.email-not-verified": { - "message": "כתובת האימייל טרם אומתה" - }, - "profile.details.tooltip.email-verified": { - "message": "כתובת האימייל אומתה" - }, - "profile.error.not-found": { - "message": "המשתמש לא נמצא" - }, - "profile.label.affiliate": { - "message": "שותף או משתמש שותף" - }, - "profile.label.badges": { - "message": "תגים" - }, - "profile.label.collection": { - "message": "אוסף" - }, - "profile.label.joined": { - "message": "הצטרף ב-" - }, - "profile.label.no-collections": { - "message": "למשתמש זה אין אוספים!" - }, - "profile.label.no-collections-auth": { - "message": "אין לך אוספים.\nהאם תרצה ליצור אחד?" - }, - "profile.label.no-projects": { - "message": "למשתמש זה אין פרויקטים!" - }, - "profile.label.no-projects-auth": { - "message": "אין לך פרויקטים.\nהאם תרצה ליצור אחד?" - }, - "profile.label.organizations": { - "message": "ארגונים" - }, - "profile.label.saving": { - "message": "שומר..." - }, - "profile.meta.description": { - "message": "הורד את הפרויקטים של {username} ב-Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - הורד את הפרויקטים של {username} ב-Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} project follower} other {{count} project followers}}" - }, - "profile.user-id": { - "message": "מזהה משתמש: {id}" - }, "project-member-header.error-decline": { "message": "הפעולה לדחיית ההזמנה לצוות נכשלה" }, diff --git a/apps/frontend/src/locales/hu-HU/index.json b/apps/frontend/src/locales/hu-HU/index.json index 33a24f5042..3f7763ca42 100644 --- a/apps/frontend/src/locales/hu-HU/index.json +++ b/apps/frontend/src/locales/hu-HU/index.json @@ -3068,117 +3068,6 @@ "organization.projects.none-with-create-prompt": { "message": "Ennek a szervezetnek még nincsenek projektjei. Szeretnél létrehozni egyet?" }, - "profile.bio.fallback.creator": { - "message": "Egy Modrinth fejlesztő." - }, - "profile.bio.fallback.user": { - "message": "Egy Modrinth felhasználó." - }, - "profile.button.analytics": { - "message": "Felhasználó statisztikáinak megtekintése" - }, - "profile.button.billing": { - "message": "Felhasználói számlázás kezelése" - }, - "profile.button.edit-role": { - "message": "Szerep szerkesztése" - }, - "profile.button.info": { - "message": "Felhasználói adatok megtekintése" - }, - "profile.button.manage-projects": { - "message": "Projektek kezelése" - }, - "profile.button.remove-affiliate": { - "message": "Társult vállalkozás törlése" - }, - "profile.button.set-affiliate": { - "message": "Beállítás társult vállalkozás ként" - }, - "profile.collection.projects-count": { - "message": "{count} projekt" - }, - "profile.details.label.auth-providers": { - "message": "Auth szolgáltatók" - }, - "profile.details.label.email": { - "message": "E-mail" - }, - "profile.details.label.email-verified": { - "message": "E-mail cím ellenőrizve" - }, - "profile.details.label.has-password": { - "message": "Van jelszó" - }, - "profile.details.label.has-totp": { - "message": "Van TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Kifizetési módok" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Nem hitelesített e-mail" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mail cím hitelesítve" - }, - "profile.error.not-found": { - "message": "Felhasználó nem található" - }, - "profile.label.affiliate": { - "message": "Társult vállalkozás" - }, - "profile.label.badges": { - "message": "Jelvények" - }, - "profile.label.collection": { - "message": "Gyűjtemény" - }, - "profile.label.download-count": { - "message": "letöltés" - }, - "profile.label.joined": { - "message": "Csatlakozott" - }, - "profile.label.no-collections": { - "message": "Ennek a felhasználónak nincsenek gyűjteményei!" - }, - "profile.label.no-collections-auth": { - "message": "Nincsenek gyűjteményeid.\nSzeretnél létrehozni egyet?" - }, - "profile.label.no-projects": { - "message": "Ennek a felhasználónak nincsenek projektjei!" - }, - "profile.label.no-projects-auth": { - "message": "Nincsenek projektjeid.\nSzeretnél létrehozni egyet?" - }, - "profile.label.organizations": { - "message": "Szervezetek" - }, - "profile.label.project-count": { - "message": "projekt" - }, - "profile.label.saving": { - "message": "Mentés..." - }, - "profile.meta.description": { - "message": "Töltsd le {username} projektjeit a Modrinthon" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Töltsd le {username} projektjeit a Modrinthon" - }, - "profile.official-account": { - "message": "Hivatalos Modrinth fiók" - }, - "profile.official-account.bio": { - "message": "A hivatalos Modrinth fiók. Kérj segítséget a oldalon, vagy e-mailben: " - }, - "profile.stats.projects-followers": { - "message": "{count} projektkövető" - }, - "profile.user-id": { - "message": "Felhasználói azonosító: {id}" - }, "project-member-header.error-decline": { "message": "Nem sikerült elutasítani a meghívást" }, diff --git a/apps/frontend/src/locales/id-ID/index.json b/apps/frontend/src/locales/id-ID/index.json index 4eb7da27da..7f57453016 100644 --- a/apps/frontend/src/locales/id-ID/index.json +++ b/apps/frontend/src/locales/id-ID/index.json @@ -2159,99 +2159,6 @@ "muralpay.warning.wallet-address": { "message": "Periksa ulang alamat dompet Anda. Dana yang terkirim ke alamat yang salah tidak dapat dipulihkan." }, - "profile.bio.fallback.creator": { - "message": "Seorang kreator Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Seorang pengguna Modrinth." - }, - "profile.button.billing": { - "message": "Kelola penagihan pengguna" - }, - "profile.button.edit-role": { - "message": "Ubah peran" - }, - "profile.button.info": { - "message": "Lihat perincian pengguna" - }, - "profile.button.manage-projects": { - "message": "Kelola proyek" - }, - "profile.button.remove-affiliate": { - "message": "Hapus sebagai afiliasi" - }, - "profile.button.set-affiliate": { - "message": "Tetapkan sebagai afiliasi" - }, - "profile.details.label.auth-providers": { - "message": "Penyedia autentikasi" - }, - "profile.details.label.email": { - "message": "Sur-el" - }, - "profile.details.label.email-verified": { - "message": "Sur-el terverifikasi" - }, - "profile.details.label.has-password": { - "message": "Memiliki kata sandi" - }, - "profile.details.label.has-totp": { - "message": "Memiliki TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Metode pembayaran" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Sur-el tidak terverifikasi" - }, - "profile.details.tooltip.email-verified": { - "message": "Sur-el terverifikasi" - }, - "profile.error.not-found": { - "message": "Pengguna tidak ditemukan" - }, - "profile.label.affiliate": { - "message": "Afiliasi" - }, - "profile.label.badges": { - "message": "Lencana" - }, - "profile.label.collection": { - "message": "Koleksi" - }, - "profile.label.joined": { - "message": "Telah bergabung" - }, - "profile.label.no-collections": { - "message": "Pengguna ini tidak memiliki koleksi!" - }, - "profile.label.no-collections-auth": { - "message": "Anda tidak memiliki koleksi apa pun.\nApakah Anda ingin membuatnya?" - }, - "profile.label.no-projects": { - "message": "Pengguna ini tidak memiliki proyek!" - }, - "profile.label.no-projects-auth": { - "message": "Anda tidak memiliki proyek apa pun.\nApakah Anda ingin membuatnya?" - }, - "profile.label.organizations": { - "message": "Organisasi" - }, - "profile.label.saving": { - "message": "Menyimpan..." - }, - "profile.meta.description": { - "message": "Unduh proyek milik {username} di Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Unduh proyek milik {username} di Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count} pengikut proyek}}" - }, - "profile.user-id": { - "message": "Pengenal Pengguna: {id}" - }, "project-member-header.error-decline": { "message": "Gagal menolak undangan tim" }, diff --git a/apps/frontend/src/locales/it-IT/index.json b/apps/frontend/src/locales/it-IT/index.json index fd17b4479a..8396848351 100644 --- a/apps/frontend/src/locales/it-IT/index.json +++ b/apps/frontend/src/locales/it-IT/index.json @@ -3335,117 +3335,6 @@ "organization.projects.none-with-create-prompt": { "message": "Questa organizzazione non ha alcun progetto. Vorresti crearne uno?" }, - "profile.bio.fallback.creator": { - "message": "Creatore su Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Utente di Modrinth." - }, - "profile.button.analytics": { - "message": "Mostra analitiche" - }, - "profile.button.billing": { - "message": "Gestisci pagamento utente" - }, - "profile.button.edit-role": { - "message": "Modifica ruolo" - }, - "profile.button.info": { - "message": "Mostra dettagli utente" - }, - "profile.button.manage-projects": { - "message": "Gestisci progetti" - }, - "profile.button.remove-affiliate": { - "message": "Rimuovi da affiliato" - }, - "profile.button.set-affiliate": { - "message": "Imposta come affiliato" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# progetto} other {# progetti}}" - }, - "profile.details.label.auth-providers": { - "message": "Fornitori autenticazione" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Email verificata" - }, - "profile.details.label.has-password": { - "message": "Usa una password" - }, - "profile.details.label.has-totp": { - "message": "Usa TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Metodi di pagamento" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Email non verificata" - }, - "profile.details.tooltip.email-verified": { - "message": "Email verificata" - }, - "profile.error.not-found": { - "message": "Utente non trovato" - }, - "profile.label.affiliate": { - "message": "Affiliato" - }, - "profile.label.badges": { - "message": "Distintivi" - }, - "profile.label.collection": { - "message": "Raccolta" - }, - "profile.label.download-count": { - "message": "download" - }, - "profile.label.joined": { - "message": "Iscrizione" - }, - "profile.label.no-collections": { - "message": "Questo utente non ha raccolte!" - }, - "profile.label.no-collections-auth": { - "message": "Non hai alcuna raccolta.\nNe vorresti creare una?" - }, - "profile.label.no-projects": { - "message": "Questo utente non ha progetti!" - }, - "profile.label.no-projects-auth": { - "message": "Non hai alcun progetto. Ne vorresti creare uno?" - }, - "profile.label.organizations": { - "message": "Organizzazioni" - }, - "profile.label.project-count": { - "message": "{count, plural, one {progetto} other {progetti}}" - }, - "profile.label.saving": { - "message": "Salvataggio..." - }, - "profile.meta.description": { - "message": "Scarica i progetti di {username} su Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Scarica i progetti di {username} su Modrinth" - }, - "profile.official-account": { - "message": "Account Modrinth ufficiale" - }, - "profile.official-account.bio": { - "message": "L'account ufficiale di Modrinth. Ricevi assistenza presso o tramite mail presso " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, =0 {Nessun} other {{count}}} follower del progetto" - }, - "profile.user-id": { - "message": "ID utente: {id}" - }, "project-member-header.error-decline": { "message": "Errore nel rifiutare l'invito al team" }, diff --git a/apps/frontend/src/locales/ja-JP/index.json b/apps/frontend/src/locales/ja-JP/index.json index 826a47bce9..eb871d6183 100644 --- a/apps/frontend/src/locales/ja-JP/index.json +++ b/apps/frontend/src/locales/ja-JP/index.json @@ -2729,96 +2729,6 @@ "organization.project-transfer.type-column": { "message": "タイプ" }, - "profile.bio.fallback.creator": { - "message": "Modrinthの作成者。" - }, - "profile.bio.fallback.user": { - "message": "Modrinthのユーザー。" - }, - "profile.button.billing": { - "message": "ユーザー課金管理" - }, - "profile.button.edit-role": { - "message": "ロールを編集" - }, - "profile.button.info": { - "message": "ユーザー情報を表示" - }, - "profile.button.manage-projects": { - "message": "プロジェクトを管理" - }, - "profile.button.remove-affiliate": { - "message": "アフィリエイトを解除する" - }, - "profile.button.set-affiliate": { - "message": "アフィリエイトとして設定する" - }, - "profile.details.label.auth-providers": { - "message": "認証プロバイダー" - }, - "profile.details.label.email": { - "message": "メールドレス" - }, - "profile.details.label.email-verified": { - "message": "メールアドレス認証済み" - }, - "profile.details.label.has-password": { - "message": "パスワードがすでにあります" - }, - "profile.details.label.has-totp": { - "message": "TOTPがあります" - }, - "profile.details.label.payment-methods": { - "message": "支払い方法" - }, - "profile.details.tooltip.email-not-verified": { - "message": "メールアドレス未認証" - }, - "profile.details.tooltip.email-verified": { - "message": "メールアドレス認証済み" - }, - "profile.error.not-found": { - "message": "ユーザーが見つかりませんでした" - }, - "profile.label.affiliate": { - "message": "アフィリエイト" - }, - "profile.label.badges": { - "message": "バッジ" - }, - "profile.label.collection": { - "message": "コレクション" - }, - "profile.label.joined": { - "message": "参加: " - }, - "profile.label.no-collections": { - "message": "このユーザーのコレクションはありません!" - }, - "profile.label.no-collections-auth": { - "message": "コレクションがありません。\n コレクションを作成しますか?" - }, - "profile.label.no-projects": { - "message": "このユーザーのプロジェクトはありません!" - }, - "profile.label.no-projects-auth": { - "message": "プロジェクトがありません\nプロジェクトを作成しますか?" - }, - "profile.label.organizations": { - "message": "組織" - }, - "profile.label.saving": { - "message": "保存中…" - }, - "profile.meta.description": { - "message": "Modrinthで{username}のプロジェクトをダウンロードする" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Modrinthで{username}のプロジェクトをダウンロードする" - }, - "profile.user-id": { - "message": "ユーザーID: {id}" - }, "project-member-header.error-decline": { "message": "チームへの招待の辞退に失敗しました" }, diff --git a/apps/frontend/src/locales/ko-KR/index.json b/apps/frontend/src/locales/ko-KR/index.json index e2e803bfd1..480a56fef2 100644 --- a/apps/frontend/src/locales/ko-KR/index.json +++ b/apps/frontend/src/locales/ko-KR/index.json @@ -3290,117 +3290,6 @@ "organization.projects.none-with-create-prompt": { "message": "이 조직에는 아직 프로젝트가 없습니다. 하나 생성하시겠습니까??" }, - "profile.bio.fallback.creator": { - "message": "Modrinth 창작자입니다." - }, - "profile.bio.fallback.user": { - "message": "Modrinth 사용자입니다." - }, - "profile.button.analytics": { - "message": "사용자 분석 보기" - }, - "profile.button.billing": { - "message": "사용자 청구 관리" - }, - "profile.button.edit-role": { - "message": "역할 수정" - }, - "profile.button.info": { - "message": "사용자 상세 정보 보기" - }, - "profile.button.manage-projects": { - "message": "프로젝트 관리" - }, - "profile.button.remove-affiliate": { - "message": "제휴 취소" - }, - "profile.button.set-affiliate": { - "message": "제휴사로 설정" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {프로젝트 #개} other {프로젝트 #개}}" - }, - "profile.details.label.auth-providers": { - "message": "인증 제공자" - }, - "profile.details.label.email": { - "message": "이메일" - }, - "profile.details.label.email-verified": { - "message": "이메일 인증됨" - }, - "profile.details.label.has-password": { - "message": "비밀번호 사용" - }, - "profile.details.label.has-totp": { - "message": "TOTP 사용" - }, - "profile.details.label.payment-methods": { - "message": "결제 수단" - }, - "profile.details.tooltip.email-not-verified": { - "message": "이메일이 인증되지 않음" - }, - "profile.details.tooltip.email-verified": { - "message": "이메일 인증됨" - }, - "profile.error.not-found": { - "message": "사용자를 찾을 수 없습니다" - }, - "profile.label.affiliate": { - "message": "제휴" - }, - "profile.label.badges": { - "message": "배지" - }, - "profile.label.collection": { - "message": "컬렉션" - }, - "profile.label.download-count": { - "message": "" - }, - "profile.label.joined": { - "message": "가입" - }, - "profile.label.no-collections": { - "message": "이 사용자는 컬렉션이 없습니다!" - }, - "profile.label.no-collections-auth": { - "message": "아직 컬렉션이 없습니다.\n새 컬렉션을 생성하시겠습니까?" - }, - "profile.label.no-projects": { - "message": "이 사용자는 프로젝트가 없습니다!" - }, - "profile.label.no-projects-auth": { - "message": "아직 프로젝트가 없습니다.\n새 프로젝트를 생성하시겠습니까?" - }, - "profile.label.organizations": { - "message": "조직" - }, - "profile.label.project-count": { - "message": "" - }, - "profile.label.saving": { - "message": "저장..." - }, - "profile.meta.description": { - "message": "Modrinth에서 {username}의 프로젝트 다운로드" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Modrinth에서 {username}의 프로젝트 다운로드" - }, - "profile.official-account": { - "message": "공식 Modrinth 계정" - }, - "profile.official-account.bio": { - "message": "Modrinth의 공식 사용자 계정입니다. 또는 이메일을 통해 지원을 받으실 수 있습니다" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count} 프로젝트 팔로워}}" - }, - "profile.user-id": { - "message": "사용자 ID: {id}" - }, "project-member-header.error-decline": { "message": "팀 초대를 거절하지 못했습니다" }, diff --git a/apps/frontend/src/locales/ms-MY/index.json b/apps/frontend/src/locales/ms-MY/index.json index 622209a276..5b5433dbc6 100644 --- a/apps/frontend/src/locales/ms-MY/index.json +++ b/apps/frontend/src/locales/ms-MY/index.json @@ -2684,105 +2684,6 @@ "muralpay.warning.wallet-address": { "message": "Semak semula alamat dompet anda. Dana yang dihantar ke alamat yang salah tidak boleh dipulihkan." }, - "profile.bio.fallback.creator": { - "message": "Seorang pencipta Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Seorang pengguna Modrinth." - }, - "profile.button.billing": { - "message": "Urus pengebilan pengguna" - }, - "profile.button.edit-role": { - "message": "Sunting peranan" - }, - "profile.button.info": { - "message": "Lihat butiran pengguna" - }, - "profile.button.manage-projects": { - "message": "Urus projek" - }, - "profile.button.remove-affiliate": { - "message": "Alih keluar rakan afiliasi" - }, - "profile.button.set-affiliate": { - "message": "Tetapkan sebagai rakan afiliasi" - }, - "profile.collection.projects-count": { - "message": "{count, plural, other {# projek}}" - }, - "profile.details.label.auth-providers": { - "message": "Penyedia pengesahan" - }, - "profile.details.label.email": { - "message": "E-mel" - }, - "profile.details.label.email-verified": { - "message": "E-mel disahkan" - }, - "profile.details.label.has-password": { - "message": "Mempunyai kata laluan" - }, - "profile.details.label.has-totp": { - "message": "Mempunyai TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Kaedah pembayaran" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-mel tidak disahkan" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mel disahkan" - }, - "profile.error.not-found": { - "message": "Pengguna tidak dijumpai" - }, - "profile.label.affiliate": { - "message": "Afiliasi" - }, - "profile.label.badges": { - "message": "Lencana" - }, - "profile.label.collection": { - "message": "Koleksi" - }, - "profile.label.joined": { - "message": "Telah menyertai" - }, - "profile.label.no-collections": { - "message": "Pengguna ini tidak memiliki sebarang koleksi!" - }, - "profile.label.no-collections-auth": { - "message": "Anda tidak memiliki sebarang koleksi.\nAdakah anda mahu mencipta sebuah koleksi?" - }, - "profile.label.no-projects": { - "message": "Pengguna ini tidak memiliki sebarang projek!" - }, - "profile.label.no-projects-auth": { - "message": "Anda tidak memiliki sebarang projek.\nAdakah anda mahu mencipta sebuah projek?" - }, - "profile.label.organizations": { - "message": "Organisasi" - }, - "profile.label.saving": { - "message": "Sedang menyimpan..." - }, - "profile.meta.description": { - "message": "Muat turun projek milik {username} di Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Muat turun projek milik {username} di Modrinth" - }, - "profile.official-account.bio": { - "message": "Akaun pengguna rasmi Modrinth. Dapatkan sokongan di atau melalui e-mel di " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count} pengikut projek}}" - }, - "profile.user-id": { - "message": "ID Pengguna: {id}" - }, "project-member-header.error-decline": { "message": "Gagal menolak jemputan pasukan" }, diff --git a/apps/frontend/src/locales/nl-NL/index.json b/apps/frontend/src/locales/nl-NL/index.json index 6d7c20654d..cf90fb756d 100644 --- a/apps/frontend/src/locales/nl-NL/index.json +++ b/apps/frontend/src/locales/nl-NL/index.json @@ -3341,117 +3341,6 @@ "organization.projects.none-with-create-prompt": { "message": "Deze organisatie heeft nog geen projecten. Wil je er een aanmaken?" }, - "profile.bio.fallback.creator": { - "message": "Een Modrinth creator." - }, - "profile.bio.fallback.user": { - "message": "Een Modrinth gebruiker." - }, - "profile.button.analytics": { - "message": "Gebruikersstatistieken bekijken" - }, - "profile.button.billing": { - "message": "Gebruikers facturering beheren" - }, - "profile.button.edit-role": { - "message": "Bewerk rol" - }, - "profile.button.info": { - "message": "Bekijk gebruikersdetails" - }, - "profile.button.manage-projects": { - "message": "Beheer projecten" - }, - "profile.button.remove-affiliate": { - "message": "Verwijderen als partner" - }, - "profile.button.set-affiliate": { - "message": "Zet als partner" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# project} other {# projecten}}" - }, - "profile.details.label.auth-providers": { - "message": "Beveiliging providers" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Email geverifieerd" - }, - "profile.details.label.has-password": { - "message": "Heeft wachtwoord" - }, - "profile.details.label.has-totp": { - "message": "Heeft TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Betaalmethodes" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Email niet geverifieerd" - }, - "profile.details.tooltip.email-verified": { - "message": "Email geverifieerd" - }, - "profile.error.not-found": { - "message": "Gebruiker niet gevonden" - }, - "profile.label.affiliate": { - "message": "Partners" - }, - "profile.label.badges": { - "message": "Badges" - }, - "profile.label.collection": { - "message": "Collectie" - }, - "profile.label.download-count": { - "message": "{count, plural, one {download} other {downloads}}" - }, - "profile.label.joined": { - "message": "Lid geworden" - }, - "profile.label.no-collections": { - "message": "Deze gebruiker heeft geen collecties!" - }, - "profile.label.no-collections-auth": { - "message": "Je hebt geen collecties.\nWil je er een aanmaken?" - }, - "profile.label.no-projects": { - "message": "Deze gebruiker heeft geen projecten!" - }, - "profile.label.no-projects-auth": { - "message": "Je hebt geen projecten.\nWil je er een aanmaken?" - }, - "profile.label.organizations": { - "message": "Organisaties" - }, - "profile.label.project-count": { - "message": "{count, plural, one {project} other {projecten}}" - }, - "profile.label.saving": { - "message": "Opslaan..." - }, - "profile.meta.description": { - "message": "Download {username} z'n projecten via Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Download {username}'s projecten op Modrinth" - }, - "profile.official-account": { - "message": "Officieel Modrinth-account" - }, - "profile.official-account.bio": { - "message": "Het officiële gebruikersaccount van Modrinth. Neem contact op met het Helpcentrum via of per e-mail via " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} project volger}other {{count} project volgers}}" - }, - "profile.user-id": { - "message": "Gebruiker ID: {id}" - }, "project-member-header.error-decline": { "message": "Team uitnodiging niet afgeslagen" }, diff --git a/apps/frontend/src/locales/no-NO/index.json b/apps/frontend/src/locales/no-NO/index.json index 069009c488..edae1e9791 100644 --- a/apps/frontend/src/locales/no-NO/index.json +++ b/apps/frontend/src/locales/no-NO/index.json @@ -2609,108 +2609,6 @@ "muralpay.warning.wallet-address": { "message": "Dobbeltsjekk lommeboksadressa di. Penger sendt til feil adresse kan ikke bli gjenoppretta." }, - "profile.bio.fallback.creator": { - "message": "En Modrinth-skaper." - }, - "profile.bio.fallback.user": { - "message": "En Modrinth-bruker." - }, - "profile.button.analytics": { - "message": "Vis brukerstatistikk" - }, - "profile.button.billing": { - "message": "Hold styr på brukerfakturering" - }, - "profile.button.edit-role": { - "message": "Rediger rolle" - }, - "profile.button.info": { - "message": "Vis brukerdetaljer" - }, - "profile.button.manage-projects": { - "message": "Hold styr på prosjekt" - }, - "profile.button.remove-affiliate": { - "message": "Fjern tilknyting" - }, - "profile.button.set-affiliate": { - "message": "Sett som tilknyting" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# prosjekt} other {# prosjekter}}" - }, - "profile.details.label.auth-providers": { - "message": "Autoriseringsgivere" - }, - "profile.details.label.email": { - "message": "Epost" - }, - "profile.details.label.email-verified": { - "message": "Epostbekreftelse" - }, - "profile.details.label.has-password": { - "message": "Har passord" - }, - "profile.details.label.has-totp": { - "message": "Har TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Betalingsmetoder" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Epost ikke bekrefta" - }, - "profile.details.tooltip.email-verified": { - "message": "Epost bekrefta" - }, - "profile.error.not-found": { - "message": "Bruker ikke funnet" - }, - "profile.label.affiliate": { - "message": "Tilknytt" - }, - "profile.label.badges": { - "message": "Merker" - }, - "profile.label.collection": { - "message": "Samling" - }, - "profile.label.joined": { - "message": "Blei med" - }, - "profile.label.no-collections": { - "message": "Denne brukeren har ingen samlinger!" - }, - "profile.label.no-collections-auth": { - "message": "Du har ikke noen samlinger.\nHar du lyst å lage en?" - }, - "profile.label.no-projects": { - "message": "Denne brukeren har ingen prosjekt!" - }, - "profile.label.no-projects-auth": { - "message": "Du har ikke noen prosjekt.\nHar du lyst å lage en?" - }, - "profile.label.organizations": { - "message": "Organisasjoner" - }, - "profile.label.saving": { - "message": "Lagrer..." - }, - "profile.meta.description": { - "message": "Last ned prosjekta til {username} på Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Last ned prosjekta til {username} på Modrinth" - }, - "profile.official-account": { - "message": "Offisiell Modrinth-bruker" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} prosjektfølger} other {{count} prosjektfølgere}}" - }, - "profile.user-id": { - "message": "Bruker-ID: {id}" - }, "project-member-header.error-decline": { "message": "Kunne ikke avslå laginvitasjonen" }, diff --git a/apps/frontend/src/locales/pl-PL/index.json b/apps/frontend/src/locales/pl-PL/index.json index dfc433841d..483710fd4e 100644 --- a/apps/frontend/src/locales/pl-PL/index.json +++ b/apps/frontend/src/locales/pl-PL/index.json @@ -3317,117 +3317,6 @@ "organization.projects.none-with-create-prompt": { "message": "Ta organizacja nie ma jeszcze żadnych projektów. Czy chcesz utworzyć nowy?" }, - "profile.bio.fallback.creator": { - "message": "Twórca na Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Użytkownik Modrinth." - }, - "profile.button.analytics": { - "message": "Pokaż dane analityczne użytkownika" - }, - "profile.button.billing": { - "message": "Zarządzaj rozliczaniem użytkownika" - }, - "profile.button.edit-role": { - "message": "Edytuj rolę" - }, - "profile.button.info": { - "message": "Otwórz szczegóły użytkownika" - }, - "profile.button.manage-projects": { - "message": "Zarządzaj projektami" - }, - "profile.button.remove-affiliate": { - "message": "Usuń afiliację" - }, - "profile.button.set-affiliate": { - "message": "Ustaw jako afiliację" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# projekt} few {# projekty} other {# projektów}}" - }, - "profile.details.label.auth-providers": { - "message": "Dostawcy uwierzytelniania" - }, - "profile.details.label.email": { - "message": "E-mail" - }, - "profile.details.label.email-verified": { - "message": "Zweryfikowano e-mail" - }, - "profile.details.label.has-password": { - "message": "Ma hasło" - }, - "profile.details.label.has-totp": { - "message": "Ma TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Metody płatności" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-mail nie jest zweryfikowany" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mail jest zweryfikowany" - }, - "profile.error.not-found": { - "message": "Nie znaleziono użytkownika" - }, - "profile.label.affiliate": { - "message": "Afiliacja" - }, - "profile.label.badges": { - "message": "Odznaki" - }, - "profile.label.collection": { - "message": "Kolekcja" - }, - "profile.label.download-count": { - "message": "{count, plural, one {pobranie} few {pobrania} other {pobrań}}" - }, - "profile.label.joined": { - "message": "Dołączył(-a)" - }, - "profile.label.no-collections": { - "message": "Ten użytkownik nie ma żadnych kolekcji!" - }, - "profile.label.no-collections-auth": { - "message": "Nie masz żadnych kolekcji.\nCzy chcesz utworzyć jedną?" - }, - "profile.label.no-projects": { - "message": "Ten użytkownik nie ma żadnych projektów!" - }, - "profile.label.no-projects-auth": { - "message": "Nie masz żadnych projektów.\nCzy chcesz utworzyć nowy?" - }, - "profile.label.organizations": { - "message": "Organizacje" - }, - "profile.label.project-count": { - "message": "{count, plural, one {projekt} few {projekty} other {projektów}}" - }, - "profile.label.saving": { - "message": "Zapisywanie..." - }, - "profile.meta.description": { - "message": "Pobieraj projekty {username} na Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Pobieraj projekty {username} na Modrinth" - }, - "profile.official-account": { - "message": "Oficjalne konto Modrinth" - }, - "profile.official-account.bio": { - "message": "Oficjalne konto Modrinth. Otrzymaj wsparcie na stronie lub poprzez e-mail " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} obserwujący projekt} other {{count} obserwujących projekt}}" - }, - "profile.user-id": { - "message": "ID użytkownika: {id}" - }, "project-member-header.error-decline": { "message": "Odrzucenie zaproszenia do zespołu nie powiodło się" }, diff --git a/apps/frontend/src/locales/pt-BR/index.json b/apps/frontend/src/locales/pt-BR/index.json index f999c26d3f..8742e76904 100644 --- a/apps/frontend/src/locales/pt-BR/index.json +++ b/apps/frontend/src/locales/pt-BR/index.json @@ -3344,117 +3344,6 @@ "organization.projects.none-with-create-prompt": { "message": "Esta organização ainda não tem nenhum projeto. Gostaria de criar um?" }, - "profile.bio.fallback.creator": { - "message": "Um criador Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Usuário comum." - }, - "profile.button.analytics": { - "message": "Ver estatísticas do usuário" - }, - "profile.button.billing": { - "message": "Gerenciar cobrança do usuário" - }, - "profile.button.edit-role": { - "message": "Editar cargo" - }, - "profile.button.info": { - "message": "Ver detalhes do usuário" - }, - "profile.button.manage-projects": { - "message": "Gerenciar projetos" - }, - "profile.button.remove-affiliate": { - "message": "Remover como afiliado" - }, - "profile.button.set-affiliate": { - "message": "Configurar como afiliado" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# projeto} other {# projetos}}" - }, - "profile.details.label.auth-providers": { - "message": "Provedores de autenticação" - }, - "profile.details.label.email": { - "message": "E-mail" - }, - "profile.details.label.email-verified": { - "message": "E-mail verificado" - }, - "profile.details.label.has-password": { - "message": "Possui senha" - }, - "profile.details.label.has-totp": { - "message": "Possui TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Métodos de pagamento" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-mail não verificado" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mail verificado" - }, - "profile.error.not-found": { - "message": "Usuário não encontrado" - }, - "profile.label.affiliate": { - "message": "Afiliado" - }, - "profile.label.badges": { - "message": "Emblemas" - }, - "profile.label.collection": { - "message": "Coleção" - }, - "profile.label.download-count": { - "message": "{count, plural, one {download} other {downloads}}" - }, - "profile.label.joined": { - "message": "Entrou" - }, - "profile.label.no-collections": { - "message": "Este usuário não possui coleções!" - }, - "profile.label.no-collections-auth": { - "message": "Você não tem nenhuma coleção.\nGostaria de criar uma?" - }, - "profile.label.no-projects": { - "message": "Este usuário não tem projetos!" - }, - "profile.label.no-projects-auth": { - "message": "Você não tem nenhum projeto.\nGostaria de criar um?" - }, - "profile.label.organizations": { - "message": "Organizações" - }, - "profile.label.project-count": { - "message": "{count, plural, one {projeto} other {projetos}}" - }, - "profile.label.saving": { - "message": "Salvando..." - }, - "profile.meta.description": { - "message": "Baixe os projetos de {username} no Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} — Baixe os projetos de {username} no Modrinth" - }, - "profile.official-account": { - "message": "Conta oficial do Modrinth" - }, - "profile.official-account.bio": { - "message": "Conta oficial do Modrinth. Obtenha suporte em ou por e-mail via " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, =0 {{count} seguidores} one {{count} seguidor} other {{count} seguidores}} do projeto" - }, - "profile.user-id": { - "message": "ID do usuário: {id}" - }, "project-member-header.error-decline": { "message": "Falha ao recusar o convite da equipe" }, diff --git a/apps/frontend/src/locales/pt-PT/index.json b/apps/frontend/src/locales/pt-PT/index.json index 1ceb04e62d..a040199f2b 100644 --- a/apps/frontend/src/locales/pt-PT/index.json +++ b/apps/frontend/src/locales/pt-PT/index.json @@ -2501,99 +2501,6 @@ "muralpay.warning.wallet-address": { "message": "Confirme o endereço da carteira. Fundos enviados para um endereço errado não podem ser recuperados." }, - "profile.bio.fallback.creator": { - "message": "Um criador Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Um utilizador Modrinth." - }, - "profile.button.billing": { - "message": "Gerir faturação do utilizador" - }, - "profile.button.edit-role": { - "message": "Editar cargo" - }, - "profile.button.info": { - "message": "Ver detalhes de utilizador" - }, - "profile.button.manage-projects": { - "message": "Gerir projetos" - }, - "profile.button.remove-affiliate": { - "message": "Remover como afiliado" - }, - "profile.button.set-affiliate": { - "message": "Definir como afiliado" - }, - "profile.details.label.auth-providers": { - "message": "Fornecedores de Autenticação" - }, - "profile.details.label.email": { - "message": "E-mail" - }, - "profile.details.label.email-verified": { - "message": "E-mail verificado" - }, - "profile.details.label.has-password": { - "message": "Tem password" - }, - "profile.details.label.has-totp": { - "message": "Tem TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Métodos de pagamento" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-mail não verificado" - }, - "profile.details.tooltip.email-verified": { - "message": "E-mail verificado" - }, - "profile.error.not-found": { - "message": "Utilizador não encontrado" - }, - "profile.label.affiliate": { - "message": "Afiliado" - }, - "profile.label.badges": { - "message": "Emblemas" - }, - "profile.label.collection": { - "message": "Coleção" - }, - "profile.label.joined": { - "message": "Entrou" - }, - "profile.label.no-collections": { - "message": "Este utilizador não tem coleções!" - }, - "profile.label.no-collections-auth": { - "message": "Não tens nenhuma coleção.\nQueres criar uma?" - }, - "profile.label.no-projects": { - "message": "Este utilizador não tem projetos!" - }, - "profile.label.no-projects-auth": { - "message": "Não tens nenhum projeto.\nQueres criar um?" - }, - "profile.label.organizations": { - "message": "Organizações" - }, - "profile.label.saving": { - "message": "A guardar..." - }, - "profile.meta.description": { - "message": "Transfere os projetos de {username} no Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Transfere os projetos de {username} no Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} seguidor do projeto} other {{count} seguidores do projeto}}" - }, - "profile.user-id": { - "message": "ID do utilizador: {id}" - }, "project-member-header.error-decline": { "message": "Falha a rejeitar o convite de equipa" }, diff --git a/apps/frontend/src/locales/ro-RO/index.json b/apps/frontend/src/locales/ro-RO/index.json index 20ff984291..44dce6f405 100644 --- a/apps/frontend/src/locales/ro-RO/index.json +++ b/apps/frontend/src/locales/ro-RO/index.json @@ -1214,60 +1214,6 @@ "muralpay.rail.fiat-usd.name": { "message": "Transfer bancar (USD)" }, - "profile.bio.fallback.creator": { - "message": "Un creator Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Un utilizator Modrinth." - }, - "profile.button.billing": { - "message": "Administrează facturarea utilizatorului" - }, - "profile.button.edit-role": { - "message": "Editează rolul" - }, - "profile.button.info": { - "message": "Vizualizează detaliile utilizatorului" - }, - "profile.button.manage-projects": { - "message": "Administrează proiectele" - }, - "profile.error.not-found": { - "message": "Utilizatorul nu a fost găsit" - }, - "profile.label.badges": { - "message": "Insigne" - }, - "profile.label.no-collections": { - "message": "Acest utilizator nu are colecții!" - }, - "profile.label.no-collections-auth": { - "message": "Nu ai nicio colecție.\nVrei să creezi una?" - }, - "profile.label.no-projects": { - "message": "Acest utilizator nu are proiecte!" - }, - "profile.label.no-projects-auth": { - "message": "Nu aveți proiecte.\nDoriți să creați unul?" - }, - "profile.label.organizations": { - "message": "Organizații" - }, - "profile.label.saving": { - "message": "Salvare..." - }, - "profile.meta.description": { - "message": "Descarcă proiectele lui {username} pe Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Descarcă proiectele lui {username} pe Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} urmăritor} other {{count} urmăritori}}" - }, - "profile.user-id": { - "message": "ID-ul utilizatorului: {id}" - }, "project-member-header.error-decline": { "message": "Refuzarea invitației în echipă a eșuat" }, diff --git a/apps/frontend/src/locales/ru-RU/index.json b/apps/frontend/src/locales/ru-RU/index.json index de05ad9b9d..3058b81b43 100644 --- a/apps/frontend/src/locales/ru-RU/index.json +++ b/apps/frontend/src/locales/ru-RU/index.json @@ -3320,117 +3320,6 @@ "organization.projects.none-with-create-prompt": { "message": "У этой организации ещё нет проектов. Хотите создать новый?" }, - "profile.bio.fallback.creator": { - "message": "Автор на Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Пользователь Modrinth." - }, - "profile.button.analytics": { - "message": "Посмотреть аналитику" - }, - "profile.button.billing": { - "message": "Управление платежами" - }, - "profile.button.edit-role": { - "message": "Изменить роль" - }, - "profile.button.info": { - "message": "Посмотреть подробности" - }, - "profile.button.manage-projects": { - "message": "Управление проектами" - }, - "profile.button.remove-affiliate": { - "message": "Убрать из партнёров" - }, - "profile.button.set-affiliate": { - "message": "Сделать партнёром" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# проект} few {# проекта} other {# проектов}}" - }, - "profile.details.label.auth-providers": { - "message": "Сервисы входа" - }, - "profile.details.label.email": { - "message": "Почта" - }, - "profile.details.label.email-verified": { - "message": "Почта подтверждена" - }, - "profile.details.label.has-password": { - "message": "Есть пароль" - }, - "profile.details.label.has-totp": { - "message": "Есть TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Способы оплаты" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Почта не подтверждена" - }, - "profile.details.tooltip.email-verified": { - "message": "Почта подтверждена" - }, - "profile.error.not-found": { - "message": "Пользователь не найден" - }, - "profile.label.affiliate": { - "message": "Партнёр" - }, - "profile.label.badges": { - "message": "Значки" - }, - "profile.label.collection": { - "message": "Коллекция" - }, - "profile.label.download-count": { - "message": "{count, plural, one {скачивание} few {скачивания} other {скачиваний}}" - }, - "profile.label.joined": { - "message": "Регистрация:" - }, - "profile.label.no-collections": { - "message": "У этого пользователя нет коллекций!" - }, - "profile.label.no-collections-auth": { - "message": "У вас ещё нет коллекций.\nХотите создать новую?" - }, - "profile.label.no-projects": { - "message": "У этого пользователя нет проектов!" - }, - "profile.label.no-projects-auth": { - "message": "У вас ещё нет проектов.\nХотите создать новый?" - }, - "profile.label.organizations": { - "message": "Организации" - }, - "profile.label.project-count": { - "message": "{count, plural, one {проект} few {проекта} other {проектов}}" - }, - "profile.label.saving": { - "message": "Сохранение..." - }, - "profile.meta.description": { - "message": "Скачать проекты {username} на Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} — Скачать проекты {username} на Modrinth" - }, - "profile.official-account": { - "message": "Официальный аккаунт Modrinth" - }, - "profile.official-account.bio": { - "message": "Официальный аккаунт Modrinth. Связаться с поддержкой можно по ссылке или по электронной почте: " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}} у проектов" - }, - "profile.user-id": { - "message": "ID пользователя: {id}" - }, "project-member-header.error-decline": { "message": "Не удалось отклонить приглашение в команду" }, diff --git a/apps/frontend/src/locales/sr-CS/index.json b/apps/frontend/src/locales/sr-CS/index.json index cdfa2f79a4..dff4254bb2 100644 --- a/apps/frontend/src/locales/sr-CS/index.json +++ b/apps/frontend/src/locales/sr-CS/index.json @@ -1220,51 +1220,6 @@ "muralpay.field.phone-number": { "message": "Broj telefona" }, - "profile.button.analytics": { - "message": "Pregled analitike korisnika" - }, - "profile.button.billing": { - "message": "Upravljaj naplatom korisnika" - }, - "profile.button.info": { - "message": "Prikaži podatke o korisniku" - }, - "profile.button.manage-projects": { - "message": "Upravljaj projektima" - }, - "profile.error.not-found": { - "message": "Korisnik nije pronađen" - }, - "profile.label.badges": { - "message": "Bedževi" - }, - "profile.label.no-collections": { - "message": "Ovaj korisnik nema kolekcija!" - }, - "profile.label.no-collections-auth": { - "message": "Nemaš nijednu kolekciju.\nDa li želiš da kreiraš jednu?" - }, - "profile.label.no-projects": { - "message": "Ovaj korisnik nema projekata!" - }, - "profile.label.no-projects-auth": { - "message": "Nemaš nijedan projekat.\nDa li želiš da kreiraš jedan?" - }, - "profile.label.organizations": { - "message": "Organizacije" - }, - "profile.meta.description": { - "message": "Preuzmi projekte korisnika {username} na Modrinthu" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Preuzmi projekte korisnika {username} na Modrinthu" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} pratilac projekta} few {{count} pratioca projekta} other {{count} pratilaca projekta}}" - }, - "profile.user-id": { - "message": "ID korisnika: {id}" - }, "project-member-header.error-decline": { "message": "Neuspešno odbijanje poziva tima" }, diff --git a/apps/frontend/src/locales/sv-SE/index.json b/apps/frontend/src/locales/sv-SE/index.json index 81c44ed5f2..8f63cf8125 100644 --- a/apps/frontend/src/locales/sv-SE/index.json +++ b/apps/frontend/src/locales/sv-SE/index.json @@ -2819,99 +2819,6 @@ "organization.project-transfer.type-column": { "message": "Typ" }, - "profile.bio.fallback.creator": { - "message": "En Modrinth kreatör." - }, - "profile.bio.fallback.user": { - "message": "En Modrinth användare." - }, - "profile.button.analytics": { - "message": "Visa användarstatistik" - }, - "profile.button.billing": { - "message": "Hantera användarfakturering" - }, - "profile.button.edit-role": { - "message": "Redigera roll" - }, - "profile.button.info": { - "message": "Visa användardetaljer" - }, - "profile.button.manage-projects": { - "message": "Hantera projekt" - }, - "profile.collection.projects-count": { - "message": "{count} projekt" - }, - "profile.details.label.auth-providers": { - "message": "Autentiseringsleverantörer" - }, - "profile.details.label.email": { - "message": "E-post" - }, - "profile.details.label.email-verified": { - "message": "E-post verifierad" - }, - "profile.details.label.has-password": { - "message": "Har lösenord" - }, - "profile.details.label.has-totp": { - "message": "Har TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Betalningsmetoder" - }, - "profile.details.tooltip.email-not-verified": { - "message": "E-post ej verifierad" - }, - "profile.details.tooltip.email-verified": { - "message": "E-post verifierad" - }, - "profile.error.not-found": { - "message": "Användare hittades inte" - }, - "profile.label.affiliate": { - "message": "Affiliate" - }, - "profile.label.badges": { - "message": "Märken" - }, - "profile.label.collection": { - "message": "Samling" - }, - "profile.label.joined": { - "message": "Gick med" - }, - "profile.label.no-collections": { - "message": "Denna användare har inga samlingar!" - }, - "profile.label.no-collections-auth": { - "message": "Du har inga samlingar.\nVill du skapa en?" - }, - "profile.label.no-projects": { - "message": "Denna användare har inga projekt!" - }, - "profile.label.no-projects-auth": { - "message": "Du har inga projekt.\nVill du skapa ett?" - }, - "profile.label.organizations": { - "message": "Organisationer" - }, - "profile.label.saving": { - "message": "Sparar..." - }, - "profile.meta.description": { - "message": "Ladda ner {username}s projekt på Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Ladda ned {username}s projekt på Modrinth" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} följare} other {{count} följare}}" - }, - "profile.user-id": { - "message": "Användar-ID: {id}" - }, "project-member-header.error-decline": { "message": "Misslyckades att neka inbjudan" }, diff --git a/apps/frontend/src/locales/tr-TR/index.json b/apps/frontend/src/locales/tr-TR/index.json index 069d6532d5..9370d3b9f4 100644 --- a/apps/frontend/src/locales/tr-TR/index.json +++ b/apps/frontend/src/locales/tr-TR/index.json @@ -2996,111 +2996,6 @@ "muralpay.warning.wallet-address": { "message": "Cüzdan adresinizi iki kez kontrol edin. Yanlış adrese gönderilen fonlar kurtarılamaz." }, - "profile.bio.fallback.creator": { - "message": "Bir Modrinth içerik üreticisi." - }, - "profile.bio.fallback.user": { - "message": "Bir Modrinth kullanıcısı." - }, - "profile.button.analytics": { - "message": "Kullanıcı analizlerini görüntüle" - }, - "profile.button.billing": { - "message": "Kullanıcı faturasını yönet" - }, - "profile.button.edit-role": { - "message": "Rolü düzenle" - }, - "profile.button.info": { - "message": "Kullanıcı ayrıntılarını görüntüle" - }, - "profile.button.manage-projects": { - "message": "Projeleri yönet" - }, - "profile.button.remove-affiliate": { - "message": "Ortaklıktan kaldır" - }, - "profile.button.set-affiliate": { - "message": "Ortak olarak ayarla" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# project} other {# projects}}" - }, - "profile.details.label.auth-providers": { - "message": "Kimlik doğrulama sağlayıcıları" - }, - "profile.details.label.email": { - "message": "Eposta" - }, - "profile.details.label.email-verified": { - "message": "E-posta doğrulandı" - }, - "profile.details.label.has-password": { - "message": "Parola var" - }, - "profile.details.label.has-totp": { - "message": "TOTP var" - }, - "profile.details.label.payment-methods": { - "message": "Ödeme yöntemleri" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Eposta doğrulanmadı" - }, - "profile.details.tooltip.email-verified": { - "message": "Eposta doğrulandı" - }, - "profile.error.not-found": { - "message": "Kullanıcı bulunamadı" - }, - "profile.label.affiliate": { - "message": "Ortak" - }, - "profile.label.badges": { - "message": "Rozetler" - }, - "profile.label.collection": { - "message": "Koleksiyon" - }, - "profile.label.joined": { - "message": "Katılma:" - }, - "profile.label.no-collections": { - "message": "Bu kullanıcının hiç koleksiyonu yok!" - }, - "profile.label.no-collections-auth": { - "message": "Hiç koleksiyonun yok.\nOluşturmak ister misin?" - }, - "profile.label.no-projects": { - "message": "Bu kullanıcının hiç projesi yok!" - }, - "profile.label.no-projects-auth": { - "message": "Hiç projen yok.\nOluşturmak ister misin?" - }, - "profile.label.organizations": { - "message": "Organizasyonlar" - }, - "profile.label.saving": { - "message": "Kaydediliyor..." - }, - "profile.meta.description": { - "message": "{username} kullanıcısının Modrinth'teki projelerini indir" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - {username} kullanıcısının Modrinth'teki projelerini indir" - }, - "profile.official-account": { - "message": "Resmi Modrinth hesabı" - }, - "profile.official-account.bio": { - "message": "Modrinth'in resmi kullanıcı hesabı Destek için adresinden veya e-posta adresinden iletişime geçebilirsiniz" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} proje takipçisi} other {{count} proje takipçisi}}" - }, - "profile.user-id": { - "message": "Kullanıcı Kimliği: {id}" - }, "project-member-header.error-decline": { "message": "Ekip daveti reddedilemedi" }, diff --git a/apps/frontend/src/locales/uk-UA/index.json b/apps/frontend/src/locales/uk-UA/index.json index 7e21230440..757fcd2f9b 100644 --- a/apps/frontend/src/locales/uk-UA/index.json +++ b/apps/frontend/src/locales/uk-UA/index.json @@ -3299,117 +3299,6 @@ "organization.projects.none-with-create-prompt": { "message": "Ця організація ще не має проєктів. Бажаєте створити новий?" }, - "profile.bio.fallback.creator": { - "message": "Творець Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Користувач Modrinth." - }, - "profile.button.analytics": { - "message": "Переглянути аналітику користувача" - }, - "profile.button.billing": { - "message": "Керувати розрахунками користувача" - }, - "profile.button.edit-role": { - "message": "Редагувати роль" - }, - "profile.button.info": { - "message": "Переглянути дані користувача" - }, - "profile.button.manage-projects": { - "message": "Керувати проєктами" - }, - "profile.button.remove-affiliate": { - "message": "Усунути як партнера" - }, - "profile.button.set-affiliate": { - "message": "Установити як партнера" - }, - "profile.collection.projects-count": { - "message": "{count, plural, one {# проєкт} few {# проєкти} many {# проєктів} other {# проєкту}}" - }, - "profile.details.label.auth-providers": { - "message": "Провайдери автентифікації" - }, - "profile.details.label.email": { - "message": "Електронна пошта" - }, - "profile.details.label.email-verified": { - "message": "Електронна адреса підтверджена" - }, - "profile.details.label.has-password": { - "message": "Має пароль" - }, - "profile.details.label.has-totp": { - "message": "Має TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Способи оплати" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Електронна пошта не перевірена" - }, - "profile.details.tooltip.email-verified": { - "message": "Електронна пошта перевірена" - }, - "profile.error.not-found": { - "message": "Користувача не знайдено" - }, - "profile.label.affiliate": { - "message": "Партнер" - }, - "profile.label.badges": { - "message": "Значки" - }, - "profile.label.collection": { - "message": "Добірка" - }, - "profile.label.download-count": { - "message": "{count, plural, one {завантаження} few {завантаження} many {завантажень} other {завантаження}}" - }, - "profile.label.joined": { - "message": "Приєднався" - }, - "profile.label.no-collections": { - "message": "У цього користувача немає добірок!" - }, - "profile.label.no-collections-auth": { - "message": "У вас немає жодної добірки.\nБажаєте створити її?" - }, - "profile.label.no-projects": { - "message": "У цього користувача немає проєктів!" - }, - "profile.label.no-projects-auth": { - "message": "У вас немає жодного проєкту.\nБажаєте створити якийсь?" - }, - "profile.label.organizations": { - "message": "Організації" - }, - "profile.label.project-count": { - "message": "{count, plural, one {проєкт} few {проєкти} many {проєктів} other {проєктів}}" - }, - "profile.label.saving": { - "message": "Збереження…" - }, - "profile.meta.description": { - "message": "Завантажте проєкти {username} на Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} — Завантажуйте проєкти {username} на Modrinth" - }, - "profile.official-account": { - "message": "Офіційний обліковий запис Modrinth" - }, - "profile.official-account.bio": { - "message": "Офіційний обліковий запис Modrinth. Зв'язатися з підтримкою можна за або через електронну пошту за " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, one {{count} відстежує} few {{count} відстежує} other {{count} відстежують}}" - }, - "profile.user-id": { - "message": "ID користувача: {id}" - }, "project-member-header.error-decline": { "message": "Не вдалося відхилити запрошення до команди" }, diff --git a/apps/frontend/src/locales/vi-VN/index.json b/apps/frontend/src/locales/vi-VN/index.json index 90a65c2ef7..a044792ee1 100644 --- a/apps/frontend/src/locales/vi-VN/index.json +++ b/apps/frontend/src/locales/vi-VN/index.json @@ -2843,111 +2843,6 @@ "muralpay.warning.wallet-address": { "message": "Hãy kiểm tra kỹ địa chỉ ví. Số tiền gửi đến địa chỉ không chính xác sẽ không thể khôi phục." }, - "profile.bio.fallback.creator": { - "message": "Một nhà sáng tạo trên Modrinth." - }, - "profile.bio.fallback.user": { - "message": "Một người dùng Modrinth." - }, - "profile.button.analytics": { - "message": "Xem phân tích người dùng" - }, - "profile.button.billing": { - "message": "Quản lý thanh toán người dùng" - }, - "profile.button.edit-role": { - "message": "Chỉnh sửa vai trò" - }, - "profile.button.info": { - "message": "Xem chi tiết người dùng" - }, - "profile.button.manage-projects": { - "message": "Quản lý dự án" - }, - "profile.button.remove-affiliate": { - "message": "Xóa affiliate" - }, - "profile.button.set-affiliate": { - "message": "Đặt làm affiliate" - }, - "profile.collection.projects-count": { - "message": "{count, plural, other {# dự án}}" - }, - "profile.details.label.auth-providers": { - "message": "Nhà cung cấp xác thực" - }, - "profile.details.label.email": { - "message": "Email" - }, - "profile.details.label.email-verified": { - "message": "Email đã được xác minh" - }, - "profile.details.label.has-password": { - "message": "Có mật khẩu" - }, - "profile.details.label.has-totp": { - "message": "Có TOTP" - }, - "profile.details.label.payment-methods": { - "message": "Phương thức thanh toán" - }, - "profile.details.tooltip.email-not-verified": { - "message": "Email chưa được xác minh" - }, - "profile.details.tooltip.email-verified": { - "message": "Email đã được xác minh" - }, - "profile.error.not-found": { - "message": "Không tìm thấy người dùng" - }, - "profile.label.affiliate": { - "message": "Affiliate" - }, - "profile.label.badges": { - "message": "Huy hiệu" - }, - "profile.label.collection": { - "message": "Bộ sưu tập" - }, - "profile.label.joined": { - "message": "Đã tham gia" - }, - "profile.label.no-collections": { - "message": "Người dùng này chưa có bộ sưu tập nào!" - }, - "profile.label.no-collections-auth": { - "message": "Bạn chưa có bộ sưu tập nào.\nBạn có muốn tạo một bộ sưu tập không?" - }, - "profile.label.no-projects": { - "message": "Người dùng này chưa có dự án nào!" - }, - "profile.label.no-projects-auth": { - "message": "Bạn chưa có dự án nào.\nBạn có muốn tạo một dự án không?" - }, - "profile.label.organizations": { - "message": "Tổ chức" - }, - "profile.label.saving": { - "message": "Đang lưu..." - }, - "profile.meta.description": { - "message": "Tải xuống các dự án của {username} trên Modrinth" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - Tải xuống các dự án của {username} trên Modrinth" - }, - "profile.official-account": { - "message": "Tài khoản chính thúc bởi Modrinth" - }, - "profile.official-account.bio": { - "message": "Tài khoản chính thức của Modrinth. Nhận hỗ trợ từ hoặc qua email tại " - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count} người theo dõi dự án}}" - }, - "profile.user-id": { - "message": "ID người dùng: {id}" - }, "project-member-header.error-decline": { "message": "Không thể từ chối lời mời vào nhóm" }, diff --git a/apps/frontend/src/locales/zh-CN/index.json b/apps/frontend/src/locales/zh-CN/index.json index 4fc438a711..7fb58b2a6e 100644 --- a/apps/frontend/src/locales/zh-CN/index.json +++ b/apps/frontend/src/locales/zh-CN/index.json @@ -3344,114 +3344,6 @@ "organization.projects.none-with-create-prompt": { "message": "该组织还没有项目。想要创建一个吗?" }, - "profile.bio.fallback.creator": { - "message": "一位 Modrinth 创作者。" - }, - "profile.bio.fallback.user": { - "message": "一位 Modrinth 用户。" - }, - "profile.button.analytics": { - "message": "查看用户分析" - }, - "profile.button.billing": { - "message": "管理用户财务" - }, - "profile.button.edit-role": { - "message": "编辑角色" - }, - "profile.button.info": { - "message": "查看用户详情" - }, - "profile.button.manage-projects": { - "message": "管理项目" - }, - "profile.button.remove-affiliate": { - "message": "解除作为联盟伙伴" - }, - "profile.button.set-affiliate": { - "message": "设置为联盟伙伴" - }, - "profile.details.label.auth-providers": { - "message": "身份验证提供者" - }, - "profile.details.label.email": { - "message": "电子邮箱" - }, - "profile.details.label.email-verified": { - "message": "电子邮箱已验证" - }, - "profile.details.label.has-password": { - "message": "已设置密码" - }, - "profile.details.label.has-totp": { - "message": "已设置动态令牌" - }, - "profile.details.label.payment-methods": { - "message": "支付方式" - }, - "profile.details.tooltip.email-not-verified": { - "message": "电子邮箱未验证" - }, - "profile.details.tooltip.email-verified": { - "message": "电子邮箱验证成功" - }, - "profile.error.not-found": { - "message": "未找到用户" - }, - "profile.label.affiliate": { - "message": "联盟" - }, - "profile.label.badges": { - "message": "徽章" - }, - "profile.label.collection": { - "message": "收藏夹" - }, - "profile.label.download-count": { - "message": "{count, plural, other {下载量}}" - }, - "profile.label.joined": { - "message": "加入于" - }, - "profile.label.no-collections": { - "message": "该用户没有收藏夹!" - }, - "profile.label.no-collections-auth": { - "message": "你还没有收藏夹。\n想要创建一个吗?" - }, - "profile.label.no-projects": { - "message": "该用户没有项目!" - }, - "profile.label.no-projects-auth": { - "message": "你还没有项目。\n想要创建一个吗?" - }, - "profile.label.organizations": { - "message": "组织" - }, - "profile.label.project-count": { - "message": "{count, plural, other {项目}}" - }, - "profile.label.saving": { - "message": "正在保存……" - }, - "profile.meta.description": { - "message": "前往 Modrinth 下载 {username} 的项目" - }, - "profile.meta.description-with-bio": { - "message": "{bio} - 前往 Modrinth 下载 {username} 的项目" - }, - "profile.official-account": { - "message": "Modrinth 官方账户" - }, - "profile.official-account.bio": { - "message": "Modrinth的官方用户账号。在处获取支持或通过电子邮件获取支持" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count}项目关注者}}" - }, - "profile.user-id": { - "message": "用户ID:{id}" - }, "project-member-header.error-decline": { "message": "拒绝团队邀请时失败" }, diff --git a/apps/frontend/src/locales/zh-TW/index.json b/apps/frontend/src/locales/zh-TW/index.json index 64d1978bd9..9b13d0e4fb 100644 --- a/apps/frontend/src/locales/zh-TW/index.json +++ b/apps/frontend/src/locales/zh-TW/index.json @@ -3344,117 +3344,6 @@ "organization.projects.none-with-create-prompt": { "message": "這個組織還沒有任何專案。你想要建立一個嗎?" }, - "profile.bio.fallback.creator": { - "message": "一位 Modrinth 創作者。" - }, - "profile.bio.fallback.user": { - "message": "一位 Modrinth 使用者。" - }, - "profile.button.analytics": { - "message": "檢視使用者數據分析" - }, - "profile.button.billing": { - "message": "管理使用者帳務" - }, - "profile.button.edit-role": { - "message": "編輯角色" - }, - "profile.button.info": { - "message": "檢視使用者詳細資訊" - }, - "profile.button.manage-projects": { - "message": "管理專案" - }, - "profile.button.remove-affiliate": { - "message": "移除聯盟行銷夥伴資格" - }, - "profile.button.set-affiliate": { - "message": "設為聯盟行銷夥伴" - }, - "profile.collection.projects-count": { - "message": "{count, plural, other {# 個專案}}" - }, - "profile.details.label.auth-providers": { - "message": "驗證提供者" - }, - "profile.details.label.email": { - "message": "電子郵件" - }, - "profile.details.label.email-verified": { - "message": "電子郵件已驗證" - }, - "profile.details.label.has-password": { - "message": "是否有密碼" - }, - "profile.details.label.has-totp": { - "message": "是否有 TOTP" - }, - "profile.details.label.payment-methods": { - "message": "付款方式" - }, - "profile.details.tooltip.email-not-verified": { - "message": "電子郵件尚未驗證" - }, - "profile.details.tooltip.email-verified": { - "message": "電子郵件已驗證" - }, - "profile.error.not-found": { - "message": "找不到使用者" - }, - "profile.label.affiliate": { - "message": "聯盟行銷" - }, - "profile.label.badges": { - "message": "徽章" - }, - "profile.label.collection": { - "message": "收藏" - }, - "profile.label.download-count": { - "message": "{count, plural, other {次下載}}" - }, - "profile.label.joined": { - "message": "加入時間:" - }, - "profile.label.no-collections": { - "message": "這個使用者沒有任何收藏!" - }, - "profile.label.no-collections-auth": { - "message": "你還沒有任何收藏。\n你想要建立一個嗎?" - }, - "profile.label.no-projects": { - "message": "這個使用者沒有任何專案!" - }, - "profile.label.no-projects-auth": { - "message": "你還沒有任何專案。\n你想要建立一個嗎?" - }, - "profile.label.organizations": { - "message": "組織" - }, - "profile.label.project-count": { - "message": "{count, plural, other {個專案}}" - }, - "profile.label.saving": { - "message": "儲存中..." - }, - "profile.meta.description": { - "message": "前往 Modrinth 下載 {username} 的專案" - }, - "profile.meta.description-with-bio": { - "message": "{bio} — 前往 Modrinth 下載 {username} 的專案" - }, - "profile.official-account": { - "message": "Modrinth 官方帳號" - }, - "profile.official-account.bio": { - "message": "Modrinth 的官方使用者帳號。請至 取得支援,或透過電子郵件 聯絡客服團隊" - }, - "profile.stats.projects-followers": { - "message": "{count, plural, other {{count} 位專案追蹤者}}" - }, - "profile.user-id": { - "message": "使用者 ID:{id}" - }, "project-member-header.error-decline": { "message": "拒絕團隊邀請失敗" }, diff --git a/apps/frontend/src/pages/user/[user].vue b/apps/frontend/src/pages/user/[user].vue index e96009ba35..1d8565e819 100644 --- a/apps/frontend/src/pages/user/[user].vue +++ b/apps/frontend/src/pages/user/[user].vue @@ -1,841 +1,105 @@ - - - - diff --git a/apps/labrinth/.env.docker-compose b/apps/labrinth/.env.docker-compose index be821dd3b6..81bd6934a9 100644 --- a/apps/labrinth/.env.docker-compose +++ b/apps/labrinth/.env.docker-compose @@ -32,6 +32,16 @@ TYPESENSE_URL=http://localhost:8108 TYPESENSE_API_KEY=modrinth TYPESENSE_INDEX_PREFIX=labrinth TYPESENSE_IMPORT_BATCH_SIZE=5000 +SEARCH_TYPESENSE_DEFAULT_QUERY_BY=name,indexed_name,slug,author,indexed_author,summary +SEARCH_TYPESENSE_DEFAULT_QUERY_BY_WEIGHTS=[15,15,10,3,3,1] +SEARCH_TYPESENSE_DEFAULT_PREFIX=[true,true,true,true,true,true] +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_EXACT_MATCH=true +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_NUM_MATCHING_FIELDS=false +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_TOKEN_POSITIONS=true +SEARCH_TYPESENSE_DEFAULT_DROP_TOKENS_THRESHOLD=0 +SEARCH_TYPESENSE_DEFAULT_TEXT_MATCH_TYPE=max_weight +SEARCH_TYPESENSE_DEFAULT_BUCKETING='{"buckets":5}' +SEARCH_TYPESENSE_DEFAULT_MAX_CANDIDATES=24 REDIS_MODE=standalone REDIS_CONNECTION_TYPE=pooled diff --git a/apps/labrinth/.env.local b/apps/labrinth/.env.local index 16527c367e..ddc5096de8 100644 --- a/apps/labrinth/.env.local +++ b/apps/labrinth/.env.local @@ -50,6 +50,16 @@ TYPESENSE_URL=http://localhost:8108 TYPESENSE_API_KEY=modrinth TYPESENSE_INDEX_PREFIX=labrinth TYPESENSE_IMPORT_BATCH_SIZE=5000 +SEARCH_TYPESENSE_DEFAULT_QUERY_BY=name,indexed_name,slug,author,indexed_author,summary +SEARCH_TYPESENSE_DEFAULT_QUERY_BY_WEIGHTS=[15,15,10,3,3,1] +SEARCH_TYPESENSE_DEFAULT_PREFIX=[true,true,true,true,true,true] +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_EXACT_MATCH=true +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_NUM_MATCHING_FIELDS=false +SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_TOKEN_POSITIONS=true +SEARCH_TYPESENSE_DEFAULT_DROP_TOKENS_THRESHOLD=0 +SEARCH_TYPESENSE_DEFAULT_TEXT_MATCH_TYPE=max_weight +SEARCH_TYPESENSE_DEFAULT_BUCKETING='{"buckets":5}' +SEARCH_TYPESENSE_DEFAULT_MAX_CANDIDATES=24 REDIS_MODE=standalone REDIS_CONNECTION_TYPE=pooled diff --git a/apps/labrinth/.sqlx/query-57895dd22cb3f6d954ed742a730edece682a771d504ef209cbece81c46e07efb.json b/apps/labrinth/.sqlx/query-57895dd22cb3f6d954ed742a730edece682a771d504ef209cbece81c46e07efb.json new file mode 100644 index 0000000000..c30df61c20 --- /dev/null +++ b/apps/labrinth/.sqlx/query-57895dd22cb3f6d954ed742a730edece682a771d504ef209cbece81c46e07efb.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO blocked_users (user_id, blocked_id)\n VALUES ($1, $2)\n ON CONFLICT (user_id, blocked_id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "57895dd22cb3f6d954ed742a730edece682a771d504ef209cbece81c46e07efb" +} diff --git a/apps/labrinth/.sqlx/query-c08a594796f4af4b7cc536c672731756440e8d1165e88dc644f898a9e40abc48.json b/apps/labrinth/.sqlx/query-c08a594796f4af4b7cc536c672731756440e8d1165e88dc644f898a9e40abc48.json new file mode 100644 index 0000000000..1f32effbc3 --- /dev/null +++ b/apps/labrinth/.sqlx/query-c08a594796f4af4b7cc536c672731756440e8d1165e88dc644f898a9e40abc48.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM blocked_users\n WHERE user_id = $1 AND blocked_id = $2\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "c08a594796f4af4b7cc536c672731756440e8d1165e88dc644f898a9e40abc48" +} diff --git a/apps/labrinth/.sqlx/query-c88172a879d9c2e9cb7fa1480e274e650cd29bc04083708a5fc8580c25be7747.json b/apps/labrinth/.sqlx/query-c88172a879d9c2e9cb7fa1480e274e650cd29bc04083708a5fc8580c25be7747.json new file mode 100644 index 0000000000..0f2f18a875 --- /dev/null +++ b/apps/labrinth/.sqlx/query-c88172a879d9c2e9cb7fa1480e274e650cd29bc04083708a5fc8580c25be7747.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT 1 FROM blocked_users\n WHERE user_id = $1 AND blocked_id = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + null + ] + }, + "hash": "c88172a879d9c2e9cb7fa1480e274e650cd29bc04083708a5fc8580c25be7747" +} diff --git a/apps/labrinth/.sqlx/query-d446d0fe439ea6d8ea7df14419955d93660afa7b634cb7b5edb77c277d89e986.json b/apps/labrinth/.sqlx/query-d446d0fe439ea6d8ea7df14419955d93660afa7b634cb7b5edb77c277d89e986.json new file mode 100644 index 0000000000..d31c29e724 --- /dev/null +++ b/apps/labrinth/.sqlx/query-d446d0fe439ea6d8ea7df14419955d93660afa7b634cb7b5edb77c277d89e986.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT blocked_id FROM blocked_users\n WHERE user_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "blocked_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "d446d0fe439ea6d8ea7df14419955d93660afa7b634cb7b5edb77c277d89e986" +} diff --git a/apps/labrinth/migrations/20260722120000_blocked-users.sql b/apps/labrinth/migrations/20260722120000_blocked-users.sql new file mode 100644 index 0000000000..26c2f77ea2 --- /dev/null +++ b/apps/labrinth/migrations/20260722120000_blocked-users.sql @@ -0,0 +1,5 @@ +CREATE TABLE blocked_users ( + user_id bigint NOT NULL REFERENCES users (id) ON DELETE CASCADE, + blocked_id bigint NOT NULL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (user_id, blocked_id) +); diff --git a/apps/labrinth/src/database/models/blocked_user_item.rs b/apps/labrinth/src/database/models/blocked_user_item.rs new file mode 100644 index 0000000000..b805873045 --- /dev/null +++ b/apps/labrinth/src/database/models/blocked_user_item.rs @@ -0,0 +1,94 @@ +use crate::database::models::DBUserId; + +pub struct DBBlockedUser { + pub user_id: DBUserId, + pub blocked_id: DBUserId, +} + +impl DBBlockedUser { + pub async fn insert<'a, E>(&self, exec: E) -> Result<(), sqlx::Error> + where + E: crate::database::Executor<'a, Database = sqlx::Postgres>, + { + sqlx::query!( + " + INSERT INTO blocked_users (user_id, blocked_id) + VALUES ($1, $2) + ON CONFLICT (user_id, blocked_id) DO NOTHING + ", + self.user_id.0, + self.blocked_id.0, + ) + .execute(exec) + .await?; + + Ok(()) + } + + pub async fn is_blocked<'a, E>( + user_id: DBUserId, + blocked_id: DBUserId, + exec: E, + ) -> Result + where + E: crate::database::Executor<'a, Database = sqlx::Postgres>, + { + let blocked = sqlx::query_scalar!( + " + SELECT 1 FROM blocked_users + WHERE user_id = $1 AND blocked_id = $2 + ", + user_id.0, + blocked_id.0, + ) + .fetch_optional(exec) + .await?; + + Ok(blocked.is_some()) + } + + pub async fn get_blocked_for_user<'a, E>( + user_id: DBUserId, + exec: E, + ) -> Result, sqlx::Error> + where + E: crate::database::Executor<'a, Database = sqlx::Postgres>, + { + let blocked = sqlx::query_scalar!( + " + SELECT blocked_id FROM blocked_users + WHERE user_id = $1 + ", + user_id.0, + ) + .fetch_all(exec) + .await? + .into_iter() + .map(DBUserId) + .collect(); + + Ok(blocked) + } + + pub async fn remove<'a, E>( + user_id: DBUserId, + blocked_id: DBUserId, + exec: E, + ) -> Result<(), sqlx::Error> + where + E: crate::database::Executor<'a, Database = sqlx::Postgres>, + { + sqlx::query!( + " + DELETE FROM blocked_users + WHERE user_id = $1 AND blocked_id = $2 + ", + user_id.0, + blocked_id.0, + ) + .execute(exec) + .await?; + + Ok(()) + } +} diff --git a/apps/labrinth/src/database/models/mod.rs b/apps/labrinth/src/database/models/mod.rs index af7a0a6fc1..09eddcaed7 100644 --- a/apps/labrinth/src/database/models/mod.rs +++ b/apps/labrinth/src/database/models/mod.rs @@ -2,6 +2,7 @@ use thiserror::Error; pub mod affiliate_code_item; pub mod analytics_event_item; +pub mod blocked_user_item; pub mod categories; pub mod charge_item; pub mod collection_item; diff --git a/apps/labrinth/src/env.rs b/apps/labrinth/src/env.rs index 5ea61fda62..20ce98f546 100644 --- a/apps/labrinth/src/env.rs +++ b/apps/labrinth/src/env.rs @@ -216,6 +216,27 @@ vars! { TYPESENSE_IMPORT_BATCH_SIZE: usize = 5000usize; TYPESENSE_DELETE_BATCH_SIZE: usize = 10_000usize; TYPESENSE_USE_CACHE: bool = true; + SEARCH_TYPESENSE_DEFAULT_QUERY_BY: StringCsv = StringCsv(vec![ + "name".into(), + "indexed_name".into(), + "slug".into(), + "author".into(), + "indexed_author".into(), + "summary".into(), + ]); + SEARCH_TYPESENSE_DEFAULT_QUERY_BY_WEIGHTS: Json> = + Json(vec![15, 15, 10, 3, 3, 1]); + SEARCH_TYPESENSE_DEFAULT_PREFIX: Json> = + Json(vec![true, true, true, true, true, true]); + SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_EXACT_MATCH: bool = true; + SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_NUM_MATCHING_FIELDS: bool = false; + SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_TOKEN_POSITIONS: bool = true; + SEARCH_TYPESENSE_DEFAULT_DROP_TOKENS_THRESHOLD: usize = 0usize; + SEARCH_TYPESENSE_DEFAULT_TEXT_MATCH_TYPE: crate::search::backend::typesense::TextMatchType = + crate::search::backend::typesense::TextMatchType::MaxWeight; + SEARCH_TYPESENSE_DEFAULT_BUCKETING: Json = + Json(crate::search::backend::typesense::Bucketing::Buckets(5)); + SEARCH_TYPESENSE_DEFAULT_MAX_CANDIDATES: usize = 24usize; // storage STORAGE_BACKEND: crate::file_hosting::FileHostKind = crate::file_hosting::FileHostKind::Local; diff --git a/apps/labrinth/src/models/v2/user.rs b/apps/labrinth/src/models/v2/user.rs index d8fae78f38..35622abf8d 100644 --- a/apps/labrinth/src/models/v2/user.rs +++ b/apps/labrinth/src/models/v2/user.rs @@ -24,7 +24,6 @@ pub struct LegacyUser { pub has_totp: Option, pub payout_data: Option, // this was changed in v3, but not ones we want to keep out of v2 - // DEPRECATED. Always returns None pub github_id: Option, } diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs index 456faa87ea..fe5de4c18d 100644 --- a/apps/labrinth/src/models/v3/users.rs +++ b/apps/labrinth/src/models/v3/users.rs @@ -70,8 +70,11 @@ pub struct User { #[serde(skip_serializing_if = "Option::is_none")] pub moderation_notes: Option>, - // DEPRECATED. Always returns None pub github_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub discord_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub steam_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] @@ -119,6 +122,8 @@ impl From for User { has_password: None, has_totp: None, github_id: None, + discord_id: None, + steam_id: None, stripe_customer_id: None, allow_friend_requests: None, eligibility_verified_at: None, @@ -180,6 +185,8 @@ impl User { has_password: Some(db_user.password.is_some()), has_totp: Some(db_user.totp_secret.is_some()), github_id: None, + discord_id: None, + steam_id: None, payout_data: Some(UserPayoutData { paypal_address: db_user.paypal_email, paypal_country: db_user.paypal_country, diff --git a/apps/labrinth/src/routes/internal/blocked_users.rs b/apps/labrinth/src/routes/internal/blocked_users.rs new file mode 100644 index 0000000000..67b305970d --- /dev/null +++ b/apps/labrinth/src/routes/internal/blocked_users.rs @@ -0,0 +1,41 @@ +use crate::database::PgPool; +use crate::database::models::DBUserId; +use crate::database::models::blocked_user_item::DBBlockedUser; +use crate::routes::ApiError; +use crate::util::guards::admin_key_guard; +use actix_web::{get, web}; +use ariadne::ids::base62_impl::parse_base62; +use serde::Serialize; + +pub fn config(cfg: &mut actix_web::web::ServiceConfig) { + cfg.service(block_status); +} + +#[derive(Serialize, utoipa::ToSchema)] +pub struct BlockStatus { + pub blocked: bool, +} + +/// Check whether one user has blocked another. +#[utoipa::path(tag = "blocked_users", responses((status = OK, body = BlockStatus)))] +#[get("/block/{user_id}/{target_id}", guard = "admin_key_guard")] +pub async fn block_status( + info: web::Path<(String, String)>, + pool: web::Data, +) -> Result, ApiError> { + let (user_id, target_id) = info.into_inner(); + + let user_id = + DBUserId(parse_base62(&user_id).map_err(|_| { + ApiError::InvalidInput("invalid user_id".to_string()) + })? as i64); + let target_id = + DBUserId(parse_base62(&target_id).map_err(|_| { + ApiError::InvalidInput("invalid target_id".to_string()) + })? as i64); + + let blocked = + DBBlockedUser::is_blocked(user_id, target_id, &**pool).await?; + + Ok(web::Json(BlockStatus { blocked })) +} diff --git a/apps/labrinth/src/routes/internal/mod.rs b/apps/labrinth/src/routes/internal/mod.rs index 872593aaf1..272ac2de8b 100644 --- a/apps/labrinth/src/routes/internal/mod.rs +++ b/apps/labrinth/src/routes/internal/mod.rs @@ -2,6 +2,7 @@ pub mod admin; pub mod affiliate; pub mod attribution; pub mod billing; +pub mod blocked_users; pub mod campaign; pub mod delphi; pub mod external_notifications; @@ -29,6 +30,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { web::scope("/_internal") .wrap(default_cors()) .configure(admin::config) + .configure(blocked_users::config) .configure(session::config) .configure(flows::config) .configure(pats::config) @@ -65,6 +67,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { ), paths( admin::count_download, + blocked_users::block_status, admin::force_reindex, admin::force_reindex_project, session::list, diff --git a/apps/labrinth/src/routes/v3/blocked_users.rs b/apps/labrinth/src/routes/v3/blocked_users.rs new file mode 100644 index 0000000000..4090e3ac1c --- /dev/null +++ b/apps/labrinth/src/routes/v3/blocked_users.rs @@ -0,0 +1,121 @@ +use crate::auth::get_user_from_headers; +use crate::database::PgPool; +use crate::database::models::DBUser; +use crate::database::models::blocked_user_item::DBBlockedUser; +use crate::database::models::friend_item::DBFriend; +use crate::models::pats::Scopes; +use crate::queue::session::AuthQueue; +use crate::routes::ApiError; +use actix_web::{HttpRequest, delete, get, post, web}; +use ariadne::ids::UserId; +use eyre::eyre; +use xredis::RedisPool; + +pub fn config(cfg: &mut actix_web::web::ServiceConfig) { + cfg.service(block_user); + cfg.service(unblock_user); + cfg.service(get_blocked_users); +} + +/// Block a user. +#[utoipa::path(tag = "blocked_users", responses((status = NO_CONTENT)))] +#[post("/block/{id}")] +pub async fn block_user( + req: HttpRequest, + info: web::Path<(String,)>, + pool: web::Data, + redis: web::Data, + session_queue: web::Data, +) -> Result<(), ApiError> { + let user = get_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Scopes::USER_WRITE, + ) + .await? + .1; + + let user_id = info.into_inner().0; + let Some(blocked) = DBUser::get(&user_id, &**pool, &redis).await? else { + return Err(ApiError::NotFound); + }; + + if blocked.id == user.id.into() { + return Err(ApiError::Request(eyre!("you cannot block yourself"))); + } + + let mut transaction = pool.begin().await?; + + DBFriend::remove(user.id.into(), blocked.id, &mut transaction).await?; + + DBBlockedUser { + user_id: user.id.into(), + blocked_id: blocked.id, + } + .insert(&mut transaction) + .await?; + + transaction.commit().await?; + + Ok(()) +} + +/// Unblock a user. +#[utoipa::path(tag = "blocked_users", responses((status = NO_CONTENT)))] +#[delete("/block/{id}")] +pub async fn unblock_user( + req: HttpRequest, + info: web::Path<(String,)>, + pool: web::Data, + redis: web::Data, + session_queue: web::Data, +) -> Result<(), ApiError> { + let user = get_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Scopes::USER_WRITE, + ) + .await? + .1; + + let user_id = info.into_inner().0; + let Some(blocked) = DBUser::get(&user_id, &**pool, &redis).await? else { + return Err(ApiError::NotFound); + }; + + DBBlockedUser::remove(user.id.into(), blocked.id, &**pool).await?; + + Ok(()) +} + +/// List the users blocked by the current user. +#[utoipa::path(tag = "blocked_users", responses((status = OK, body = Vec)))] +#[get("/blocks")] +pub async fn get_blocked_users( + req: HttpRequest, + pool: web::Data, + redis: web::Data, + session_queue: web::Data, +) -> Result>, ApiError> { + let user = get_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Scopes::USER_READ, + ) + .await? + .1; + + let blocked = DBBlockedUser::get_blocked_for_user(user.id.into(), &**pool) + .await? + .into_iter() + .map(UserId::from) + .collect(); + + Ok(web::Json(blocked)) +} diff --git a/apps/labrinth/src/routes/v3/friends.rs b/apps/labrinth/src/routes/v3/friends.rs index d667459580..0f02c081da 100644 --- a/apps/labrinth/src/routes/v3/friends.rs +++ b/apps/labrinth/src/routes/v3/friends.rs @@ -1,5 +1,6 @@ use crate::auth::get_user_from_headers; use crate::database::PgPool; +use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; use crate::database::models::{DBUser, DBUserId}; use crate::models::pats::Scopes; @@ -49,6 +50,18 @@ pub async fn add_friend( return Err(ApiError::NotFound); }; + if DBBlockedUser::is_blocked(friend.id, user.id.into(), &**pool).await? { + return Err(ApiError::InvalidInput( + "You've been blocked the other user!".to_string(), + )); + } else if DBBlockedUser::is_blocked(user.id.into(), friend.id, &**pool) + .await? + { + return Err(ApiError::InvalidInput( + "You've blocked the other user!".to_string(), + )); + } + let mut transaction = pool.begin().await?; if let Some(friend) = diff --git a/apps/labrinth/src/routes/v3/mod.rs b/apps/labrinth/src/routes/v3/mod.rs index e16172a0a9..919bffee4e 100644 --- a/apps/labrinth/src/routes/v3/mod.rs +++ b/apps/labrinth/src/routes/v3/mod.rs @@ -6,6 +6,7 @@ use serde_json::json; pub mod analytics_event; pub mod analytics_get; +pub mod blocked_users; pub mod collections; pub mod content; pub mod friends; @@ -66,6 +67,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .configure(version_file::config) .configure(versions::config) .configure(friends::config) + .configure(blocked_users::config) .configure(content::config), ); } @@ -227,6 +229,9 @@ pub fn config(cfg: &mut web::ServiceConfig) { friends::add_friend, friends::remove_friend, friends::friends, + blocked_users::block_user, + blocked_users::unblock_user, + blocked_users::get_blocked_users, content::resolve_content, ), modifiers(&V3PathModifier, &SecurityAddon) diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 1ee9de6728..59a452c4de 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -481,7 +481,15 @@ pub async fn user_get( let user_id = data.id; let mut response: crate::models::users::User = if is_admin { - crate::models::users::User::from_full(data) + let github_id = + data.github_id.and_then(|id| u64::try_from(id).ok()); + let discord_id = data.discord_id.map(|id| id.to_string()); + let steam_id = data.steam_id.map(|id| id.to_string()); + let mut user = crate::models::users::User::from_full(data); + user.github_id = github_id; + user.discord_id = discord_id; + user.steam_id = steam_id; + user } else { data.into() }; diff --git a/apps/labrinth/src/search/backend/typesense/mod.rs b/apps/labrinth/src/search/backend/typesense/mod.rs index db561d5da3..e65232106c 100644 --- a/apps/labrinth/src/search/backend/typesense/mod.rs +++ b/apps/labrinth/src/search/backend/typesense/mod.rs @@ -191,8 +191,9 @@ pub enum Bucketing { BucketSize(u64), } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, strum::EnumString)] #[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] pub enum TextMatchType { MaxScore, #[default] @@ -232,9 +233,9 @@ pub struct RequestConfig { pub prioritize_token_positions: bool, #[serde(default = "default_drop_tokens_threshold")] pub drop_tokens_threshold: usize, - #[serde(default)] + #[serde(default = "default_text_match_type")] pub text_match_type: TextMatchType, - #[serde(default)] + #[serde(default = "default_bucketing")] pub bucketing: Bucketing, #[serde(default = "default_max_candidates")] pub max_candidates: usize, @@ -251,55 +252,51 @@ impl Default for RequestConfig { default_prioritize_num_matching_fields(), prioritize_token_positions: default_prioritize_token_positions(), drop_tokens_threshold: default_drop_tokens_threshold(), - text_match_type: TextMatchType::default(), - bucketing: Bucketing::default(), + text_match_type: default_text_match_type(), + bucketing: default_bucketing(), max_candidates: default_max_candidates(), } } } fn default_query_by() -> Vec { - [ - "name", - "indexed_name", - "slug", - "author", - "indexed_author", - "summary", - ] - .into_iter() - .map(str::to_string) - .collect() + ENV.SEARCH_TYPESENSE_DEFAULT_QUERY_BY.0.clone() } fn default_query_by_weights() -> Vec { - vec![15, 15, 10, 3, 3, 1] + ENV.SEARCH_TYPESENSE_DEFAULT_QUERY_BY_WEIGHTS.0.clone() } fn default_prefix() -> Vec { - vec![true, true, true, true, true, true] + ENV.SEARCH_TYPESENSE_DEFAULT_PREFIX.0.clone() } -const fn default_prioritize_exact_match() -> bool { - true +fn default_prioritize_exact_match() -> bool { + ENV.SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_EXACT_MATCH } -const fn default_prioritize_num_matching_fields() -> bool { - false +fn default_prioritize_num_matching_fields() -> bool { + ENV.SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_NUM_MATCHING_FIELDS } -const fn default_prioritize_token_positions() -> bool { - true - // false +fn default_prioritize_token_positions() -> bool { + ENV.SEARCH_TYPESENSE_DEFAULT_PRIORITIZE_TOKEN_POSITIONS } -const fn default_drop_tokens_threshold() -> usize { - 0 - // 1 +fn default_drop_tokens_threshold() -> usize { + ENV.SEARCH_TYPESENSE_DEFAULT_DROP_TOKENS_THRESHOLD } -const fn default_max_candidates() -> usize { - 8 +fn default_text_match_type() -> TextMatchType { + ENV.SEARCH_TYPESENSE_DEFAULT_TEXT_MATCH_TYPE.clone() +} + +fn default_bucketing() -> Bucketing { + ENV.SEARCH_TYPESENSE_DEFAULT_BUCKETING.0.clone() +} + +fn default_max_candidates() -> usize { + ENV.SEARCH_TYPESENSE_DEFAULT_MAX_CANDIDATES } impl TypesenseConfig { diff --git a/apps/labrinth/src/util/neverbounce.rs b/apps/labrinth/src/util/neverbounce.rs index f104deb649..ffab561d8d 100644 --- a/apps/labrinth/src/util/neverbounce.rs +++ b/apps/labrinth/src/util/neverbounce.rs @@ -2,7 +2,8 @@ use std::time::Instant; use eyre::{WrapErr, eyre}; use neverbounce::{ - ResponseStatus, SingleCheckParams, SingleCheckResponse, VerificationResult, + ReqwestErrorReason, ResponseStatus, SingleCheckParams, SingleCheckResponse, + VerificationResult, }; use tracing::{debug, error}; @@ -27,11 +28,21 @@ pub async fn check_email(email: &str) -> eyre::Result { { Ok(response) => response, Err(source) => { + let reason = ReqwestErrorReason::from(&source); + let is_transient = reason.is_transient(); + error!( result = "unknown", + request.error_reason = ?reason, + request.time_ms = check_time_start.elapsed().as_millis(), error = ?source, "NeverBounce email check failed", ); + + if is_transient { + return Ok(VerificationResult::Unknown); + } + return Err(eyre!(source)).wrap_err("failed to check email"); } }; @@ -65,13 +76,19 @@ pub async fn check_email(email: &str) -> eyre::Result { } failure_type => { let result = result.unwrap_or(VerificationResult::Unknown); + let is_transient = failure_type.is_transient(); error!( failure_type = response_failure_type(&failure_type), result = result.as_str(), request.time_ms = check_time.as_millis(), "NeverBounce email check failed", ); - Err(email_check_error_generic()) + + if is_transient { + Ok(VerificationResult::Unknown) + } else { + Err(email_check_error_generic()) + } } } } diff --git a/packages/api-client/src/modules/labrinth/types.ts b/packages/api-client/src/modules/labrinth/types.ts index c3c7155c43..337d1d1256 100644 --- a/packages/api-client/src/modules/labrinth/types.ts +++ b/packages/api-client/src/modules/labrinth/types.ts @@ -1663,6 +1663,8 @@ export namespace Labrinth { allow_friend_requests?: boolean moderation_notes?: Common.ModerationNote | null github_id?: number + discord_id?: string + steam_id?: string } export type SearchUser = { diff --git a/packages/app-lib/src/api/users.rs b/packages/app-lib/src/api/users.rs index 6d5f4019f6..e3e1c6fdec 100644 --- a/packages/app-lib/src/api/users.rs +++ b/packages/app-lib/src/api/users.rs @@ -1,7 +1,8 @@ use crate::State; -use crate::util::fetch::fetch_json; +use crate::util::fetch::{fetch_advanced, fetch_json}; use reqwest::Method; use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct SearchUser { @@ -30,3 +31,101 @@ pub async fn search_user(query: &str) -> crate::Result> { ) .await } + +#[tracing::instrument] +pub async fn get_user_profile(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!("{}user/{}", env!("MODRINTH_API_URL_V3"), user_id), + None, + None, + Some("/v3/user/:id"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_projects(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!("{}user/{}/projects", env!("MODRINTH_API_URL"), user_id), + None, + None, + Some("/v2/user/:id/projects"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_organizations(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!( + "{}user/{}/organizations", + env!("MODRINTH_API_URL_V3"), + user_id + ), + None, + None, + Some("/v3/user/:id/organizations"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_collections(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!( + "{}user/{}/collections", + env!("MODRINTH_API_URL_V3"), + user_id + ), + None, + None, + Some("/v3/user/:id/collections"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument(skip(patch))] +pub async fn patch_user(user_id: &str, patch: Value) -> crate::Result<()> { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_advanced( + Method::PATCH, + &format!("{}user/{}", env!("MODRINTH_API_URL"), user_id), + None, + Some(patch), + None, + None, + None, + Some("/v2/user/:id"), + &state.api_semaphore, + &state.pool, + ) + .await?; + + Ok(()) +} diff --git a/packages/neverbounce/src/lib.rs b/packages/neverbounce/src/lib.rs index 74c6af5b1b..b4390c0a15 100644 --- a/packages/neverbounce/src/lib.rs +++ b/packages/neverbounce/src/lib.rs @@ -4,7 +4,65 @@ use std::time::Duration; pub const DEFAULT_API_URL: &str = "https://api.neverbounce.com"; pub const SINGLE_CHECK_PATH: &str = "/v4/single/check"; -pub const TIMEOUT: Duration = Duration::from_secs(10); +pub const TIMEOUT: Duration = Duration::from_secs(5); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum ReqwestErrorReason { + Builder, + Redirect, + Status(reqwest::StatusCode), + Timeout, + Request, + Connect, + Body, + Decode, + Unknown, +} + +impl ReqwestErrorReason { + #[must_use] + pub fn is_transient(&self) -> bool { + match self { + Self::Status(status) => { + status.is_server_error() + || matches!( + *status, + reqwest::StatusCode::REQUEST_TIMEOUT + | reqwest::StatusCode::TOO_MANY_REQUESTS + ) + } + Self::Timeout | Self::Request | Self::Connect | Self::Body => true, + Self::Builder | Self::Redirect | Self::Decode | Self::Unknown => { + false + } + } + } +} + +impl From<&reqwest::Error> for ReqwestErrorReason { + fn from(error: &reqwest::Error) -> Self { + if error.is_timeout() { + Self::Timeout + } else if error.is_connect() { + Self::Connect + } else if error.is_builder() { + Self::Builder + } else if error.is_redirect() { + Self::Redirect + } else if error.is_status() { + error.status().map_or(Self::Unknown, Self::Status) + } else if error.is_request() { + Self::Request + } else if error.is_body() { + Self::Body + } else if error.is_decode() { + Self::Decode + } else { + Self::Unknown + } + } +} /// Authentication and email parameters for a single verification. /// @@ -71,6 +129,13 @@ pub enum ResponseStatus { Unrecognized(String), } +impl ResponseStatus { + #[must_use] + pub fn is_transient(&self) -> bool { + matches!(self, Self::TemporarilyUnavailable | Self::ThrottleTriggered) + } +} + impl<'de> Deserialize<'de> for ResponseStatus { fn deserialize(deserializer: D) -> Result where @@ -201,7 +266,7 @@ struct SingleCheckRequest<'a> { /// This endpoint should only be called in response to an action such as a form /// submission. Existing lists and databases must use NeverBounce's bulk API. /// Both the server verification timeout and the complete HTTP request timeout -/// are ten seconds. +/// are five seconds. pub async fn single_check( client: &Client, params: &SingleCheckParams<'_>, diff --git a/packages/ui/src/components/project/ProjectSidebarCreators.vue b/packages/ui/src/components/project/ProjectSidebarCreators.vue index 40de8c94e4..0e58d1b997 100644 --- a/packages/ui/src/components/project/ProjectSidebarCreators.vue +++ b/packages/ui/src/components/project/ProjectSidebarCreators.vue @@ -25,7 +25,7 @@ :key="`member-${member.id}`" class="flex gap-2 items-center w-fit text-primary leading-[1.2] group" :to="userLink(member.user.username)" - :target="linkTarget ?? null" + :target="resolveLinkTarget(userLinkTarget)" >
@@ -36,7 +36,7 @@ v-tooltip="formatMessage(messages.owner)" class="text-brand-orange" /> - + {{ member.role }}
@@ -79,8 +79,13 @@ const props = defineProps<{ orgLink: (slug: string) => string userLink: (username: string) => string linkTarget?: string + userLinkTarget?: string | null }>() +function resolveLinkTarget(target: string | null | undefined): string | null { + return target === undefined ? (props.linkTarget ?? null) : target +} + // Members should be an array of all members, without the accepted ones, and with the user with the Owner role at the start // The rest of the members should be sorted by role, then by name const sortedMembers = computed(() => { diff --git a/packages/ui/src/components/search/SearchFilterGroup.vue b/packages/ui/src/components/search/SearchFilterGroup.vue index 071d0896c1..4a23f1742b 100644 --- a/packages/ui/src/components/search/SearchFilterGroup.vue +++ b/packages/ui/src/components/search/SearchFilterGroup.vue @@ -19,7 +19,7 @@ :option="option" :included="included(option)" :excluded="excluded(option)" - :supports-negative-filter="supportsNegativeFilter" + :supports="supports" @toggle="(o) => emit('toggle', o)" @toggle-exclude="(o) => emit('toggleExclude', o)" > @@ -44,13 +44,13 @@ import { DropdownIcon } from '@modrinth/assets' import { ref } from 'vue' -import type { FilterOption } from '../../utils/search' +import type { FilterMode, FilterOption } from '../../utils/search' import SearchFilterOption from './SearchFilterOption.vue' defineProps<{ groupName: string options: FilterOption[] - supportsNegativeFilter: boolean + supports: FilterMode[] included: (option: FilterOption) => boolean excluded: (option: FilterOption) => boolean }>() diff --git a/packages/ui/src/components/search/SearchFilterOption.vue b/packages/ui/src/components/search/SearchFilterOption.vue index f455a273d0..b05b50fc45 100644 --- a/packages/ui/src/components/search/SearchFilterOption.vue +++ b/packages/ui/src/components/search/SearchFilterOption.vue @@ -1,13 +1,13 @@