i18n
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Failing after 10s
CI/CD Pipeline / Build and Push App Docker Image (push) Skipped
CI/CD Pipeline / Deploy to Kubernetes (push) Skipped

This commit is contained in:
2026-08-08 15:29:40 -07:00
parent 22c282d8fa
commit 0a7b5fa00f
178 changed files with 13099 additions and 1285 deletions
+3 -1
View File
@@ -1,13 +1,15 @@
import { UnstyledButton } from "@mantine/core"
import { ArrowLeftIcon } from "@phosphor-icons/react"
import { useRouter } from "@tanstack/react-router"
import { useLingui } from "@lingui/react/macro"
const BackButton = ({ top=20, left=20 }: { top?: number, left?: number }) => {
const router = useRouter()
const { t } = useLingui()
return (
<UnstyledButton
aria-label='Go back'
aria-label={t`Go back`}
style={{ cursor: 'pointer', zIndex: 1000, display: 'flex' }}
onClick={() => router.history.back()}
pos='absolute'
+9 -2
View File
@@ -1,8 +1,15 @@
import { Title, AppShell, Flex } from "@mantine/core";
import { useLingui } from "@lingui/react";
import { HeaderConfig } from "../types/header-config";
import BackButton from "./back-button";
const Header = ({ collapsed, title, withBackButton }: HeaderConfig) => {
const Header = ({ collapsed, title, titleValues, withBackButton }: HeaderConfig) => {
const { i18n } = useLingui();
const resolvedTitle =
typeof title === "string" || title === undefined
? title
: i18n._({ ...title, values: titleValues });
return (
<AppShell.Header
id='app-header'
@@ -15,7 +22,7 @@ const Header = ({ collapsed, title, withBackButton }: HeaderConfig) => {
{ withBackButton && <BackButton /> }
<Flex justify='center' px='md' mt={8}>
<Title order={1} lts='0.08em' style={{ userSelect: 'none' }}>
{title?.toLocaleUpperCase()}
{resolvedTitle?.toLocaleUpperCase()}
</Title>
</Flex>
</AppShell.Header>
+5 -2
View File
@@ -1,5 +1,6 @@
import { AuthProvider } from "@/contexts/auth-context"
import { SpotifyProvider } from "@/contexts/spotify-context"
import { LinguiProvider } from "@/lib/i18n/provider"
import MantineProvider from "@/lib/mantine/mantine-provider"
//import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
//import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
@@ -8,7 +9,8 @@ import { Toaster } from "sonner"
const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<AuthProvider>
<LinguiProvider>
<AuthProvider>
<SpotifyProvider>
<MantineProvider>
{/*<TanStackDevtools
@@ -31,7 +33,8 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
{children}
</MantineProvider>
</SpotifyProvider>
</AuthProvider>
</AuthProvider>
</LinguiProvider>
)
}
@@ -1,6 +1,7 @@
import { UnstyledButton } from "@mantine/core"
import { GearIcon } from "@phosphor-icons/react"
import { useNavigate } from "@tanstack/react-router"
import { useLingui } from "@lingui/react/macro"
import { memo } from "react";
interface SettingButtonProps {
@@ -11,10 +12,11 @@ interface SettingButtonProps {
const SettingsButton = ({ to }: SettingButtonProps) => {
const navigate = useNavigate();
const { t } = useLingui();
return (
<UnstyledButton
aria-label='Settings'
aria-label={t`Settings`}
style={{ cursor: 'pointer', zIndex: 1000, display: 'flex' }}
onClick={() => navigate({ to })}
pos='absolute'
+12 -8
View File
@@ -1,27 +1,30 @@
import { HouseIcon, RankingIcon, ShieldIcon, TrophyIcon, UserCircleIcon } from "@phosphor-icons/react";
import { useMemo } from "react";
import { useLingui } from "@lingui/react/macro";
export const useLinks = (userId: string | undefined, roles: string[]) =>
useMemo(() => {
export const useLinks = (userId: string | undefined, roles: string[]) => {
const { t } = useLingui();
return useMemo(() => {
const links = [
{
label: 'Home',
label: t`Home`,
href: '/',
Icon: HouseIcon
},
{
label: 'Statistics',
label: t`Statistics`,
href: '/stats',
Icon: RankingIcon
},
{
label: 'Tournaments',
label: t`Tournaments`,
href: '/tournaments',
Icon: TrophyIcon,
exclude: ['/admin/tournaments']
},
{
label: 'Profile',
label: t`Profile`,
href: `/profile/${userId}`,
Icon: UserCircleIcon,
include: ['/settings']
@@ -30,11 +33,12 @@ export const useLinks = (userId: string | undefined, roles: string[]) =>
if (roles.includes('Admin')) {
links.push({
label: 'Admin',
label: t`Admin`,
href: '/admin',
Icon: ShieldIcon
})
}
return links;
}, [userId, roles]);
}, [userId, roles, t]);
};
+2 -2
View File
@@ -16,14 +16,14 @@ const useRouterConfig = () => {
match?.loaderData && 'header' in match.loaderData
);
const headerConfig = matchesWithHeader.reduce((acc, match) => {
const headerConfig = matchesWithHeader.reduce<HeaderConfig>((acc, match) => {
const loaderData = match?.loaderData;
if (loaderData && typeof loaderData === 'object' && 'header' in loaderData) {
const header = loaderData.header;
if (header && typeof header === 'object') {
return {
...acc,
...header,
...(header as HeaderConfig),
}
}
}
+16 -2
View File
@@ -1,8 +1,22 @@
/**
* Serializable subset of Lingui's MessageDescriptor — header config passes
* through route loaders/beforeLoad, whose results must be serializable.
* msg`` descriptors are assignable to this shape.
*/
interface HeaderTitleMessage {
id: string;
message?: string;
comment?: string;
}
interface HeaderConfig {
title?: string;
/** Plain strings render as-is (data-derived names); use a msg`` descriptor for translatable titles. */
title?: string | HeaderTitleMessage;
/** ICU values when title is a descriptor, e.g. msg`Manage {name}` + { name }. */
titleValues?: Record<string, string | number>;
withBackButton?: boolean;
collapsed?: boolean;
settingsLink?: string;
}
export type { HeaderConfig };
export type { HeaderConfig, HeaderTitleMessage };