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);