diff --git a/public/locales/en/layout/header.json b/public/locales/en/layout/header.json new file mode 100644 index 000000000..a57e0824b --- /dev/null +++ b/public/locales/en/layout/header.json @@ -0,0 +1,27 @@ +{ + "experimentalNote": { + "label": "This is an experimental feature of Homarr. Please report any issues to the official Homarr team." + }, + "search": { + "label": "Search", + "engines": { + "web": "Search for {{query}} on the web", + "youtube": "Search for {{query}} on YouTube", + "torrent": "Search for {{query}} torrents", + "movie": "Search for {{query}} on {{app}}" + } + }, + "actions": { + "avatar": { + "switchTheme": "Switch theme", + "preferences": "User preferences", + "defaultBoard": "Default dashboard", + "about": { + "label": "About", + "new": "New" + }, + "logout": "Logout from {{username}}", + "login": "Login" + } + } +} \ No newline at end of file diff --git a/src/components/layout/header/AvatarMenu.tsx b/src/components/layout/header/AvatarMenu.tsx index 75b7e175b..e3ab1543a 100644 --- a/src/components/layout/header/AvatarMenu.tsx +++ b/src/components/layout/header/AvatarMenu.tsx @@ -12,6 +12,7 @@ import { import { useQuery } from '@tanstack/react-query'; import { User } from 'next-auth'; import { signOut, useSession } from 'next-auth/react'; +import { useTranslation } from 'next-i18next'; import Link from 'next/link'; import { forwardRef } from 'react'; import { AboutModal } from '~/components/layout/header/About/AboutModal'; @@ -21,6 +22,7 @@ import { usePackageAttributesStore } from '~/tools/client/zustands/usePackageAtt import { REPO_URL } from '../../../../data/constants'; export const AvatarMenu = () => { + const { t } = useTranslation('layout/header'); const [aboutModalOpened, aboutModal] = useDisclosure(false); const { data: sessionData } = useSession(); const { colorScheme, toggleColorScheme } = useColorScheme(); @@ -37,7 +39,7 @@ export const AvatarMenu = () => { } onClick={toggleColorScheme}> - Switch theme + {t('actions.avatar.switchTheme')} {sessionData?.user && ( <> @@ -46,10 +48,10 @@ export const AvatarMenu = () => { href="/user/preferences" icon={} > - User preferences + {t('actions.avatar.preferences')} }> - Default Dashboard + {t('actions.avatar.defaultBoard')} @@ -59,13 +61,13 @@ export const AvatarMenu = () => { rightSection={ newVersionAvailable && ( - New + {t('actions.avatar.about.new')} ) } onClick={() => aboutModal.open()} > - About + {t('actions.avatar.about.label')} {sessionData?.user ? ( { }).then(() => window.location.reload()) } > - Logout from {sessionData.user.name} + {t('actions.avatar.logout', { + username: sessionData.user.name, + })} ) : ( } component={Link} href="/auth/login"> - Login + {t('actions.avatar.login')} )} diff --git a/src/components/layout/header/Search.tsx b/src/components/layout/header/Search.tsx index ff55e1e43..d4e2d73fd 100644 --- a/src/components/layout/header/Search.tsx +++ b/src/components/layout/header/Search.tsx @@ -9,6 +9,7 @@ import { TablerIconsProps, } from '@tabler/icons-react'; import { useSession } from 'next-auth/react'; +import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { ReactNode, forwardRef, useMemo, useRef, useState } from 'react'; import { useConfigContext } from '~/config/provider'; @@ -21,6 +22,7 @@ type SearchProps = { }; export const Search = ({ isMobile }: SearchProps) => { + const { t } = useTranslation('layout/header'); const [search, setSearch] = useState(''); const ref = useRef(null); useHotkeys([['mod+K', () => ref.current?.focus()]]); @@ -37,10 +39,18 @@ export const Search = ({ isMobile }: SearchProps) => { const engines = generateEngines( search, userWithSettings?.settings.searchTemplate ?? 'https://www.google.com/search?q=%s' - ).filter( - (engine) => - engine.sort !== 'movie' || config?.apps.some((app) => app.integration.type === engine.value) - ); + ) + .filter( + (engine) => + engine.sort !== 'movie' || config?.apps.some((app) => app.integration.type === engine.value) + ) + .map((engine) => ({ + ...engine, + label: t(`search.engines.${engine.sort}`, { + app: engine.value, + query: search, + }), + })); const data = [...apps, ...engines]; return ( @@ -50,7 +60,7 @@ export const Search = ({ isMobile }: SearchProps) => { radius="xl" w={isMobile ? '100%' : 400} variant="filled" - placeholder="Search..." + placeholder={`${t('search.label')}...`} hoverOnSearchChange rightSection={ { const target = userWithSettings?.settings.openSearchInNewTab ? '_blank' : '_self'; window.open(item.metaData.url, target); }} - aria-label="Search" + aria-label={t('search.label') as string} /> { ); }; -const SearchItemComponent = forwardRef( +const SearchItemComponent = forwardRef( ({ icon, label, value, sort, ...others }, ref) => { let Icon = getItemComponent(icon); @@ -150,7 +160,6 @@ const useConfigApps = (search: string) => { type SearchAutoCompleteItem = { icon: ((props: TablerIconsProps) => ReactNode) | string; - label: string; value: string; } & ( | { @@ -169,7 +178,6 @@ const generateEngines = (searchValue: string, webTemplate: string) => ? ([ { icon: IconWorld, - label: `Search for ${searchValue} in the web`, value: `web`, sort: 'web', metaData: { @@ -180,7 +188,6 @@ const generateEngines = (searchValue: string, webTemplate: string) => }, { icon: IconDownload, - label: `Search for ${searchValue} torrents`, value: `torrent`, sort: 'torrent', metaData: { @@ -189,7 +196,6 @@ const generateEngines = (searchValue: string, webTemplate: string) => }, { icon: IconBrandYoutube, - label: `Search for ${searchValue} on youtube`, value: 'youtube', sort: 'youtube', metaData: { @@ -200,7 +206,6 @@ const generateEngines = (searchValue: string, webTemplate: string) => (name) => ({ icon: IconMovie, - label: `Search for ${searchValue} on ${name}`, value: name, sort: 'movie', }) as const diff --git a/src/pages/user/preferences.tsx b/src/pages/user/preferences.tsx index da4ef4201..66011697e 100644 --- a/src/pages/user/preferences.tsx +++ b/src/pages/user/preferences.tsx @@ -10,8 +10,7 @@ import { Title, } from '@mantine/core'; import { createFormContext } from '@mantine/form'; -import { createServerSideHelpers } from '@trpc/react-query/server'; -import { GetServerSideProps, GetServerSidePropsContext } from 'next'; +import { GetServerSideProps } from 'next'; import { useTranslation } from 'next-i18next'; import Head from 'next/head'; import { forwardRef } from 'react'; @@ -23,7 +22,6 @@ import { createTrpcServersideHelpers } from '~/server/api/helper'; import { getServerAuthSession } from '~/server/auth'; import { languages } from '~/tools/language'; import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations'; -import { manageNamespaces } from '~/tools/server/translation-namespaces'; import { RouterOutputs, api } from '~/utils/api'; import { useI18nZodResolver } from '~/utils/i18n-zod-resolver'; import { updateSettingsValidationSchema } from '~/validations/user'; diff --git a/src/tools/server/getServerSideTranslations.ts b/src/tools/server/getServerSideTranslations.ts index b7baec05c..6302933aa 100644 --- a/src/tools/server/getServerSideTranslations.ts +++ b/src/tools/server/getServerSideTranslations.ts @@ -10,7 +10,7 @@ export const getServerSideTranslations = async ( req?: IncomingMessage, res?: ServerResponse ) => { - namespaces = namespaces.concat(['common', 'zod']); + namespaces = namespaces.concat(['common', 'zod', 'layout/header', 'layout/modals/about']); if (!req || !res) { return serverSideTranslations(requestLocale ?? 'en', namespaces);