mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 07:55:52 +01:00
🌐 Add missing translations
This commit is contained in:
50
public/locales/en/user/create.json
Normal file
50
public/locales/en/user/create.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"steps": {
|
||||
"account": {
|
||||
"title": "First step",
|
||||
"text": "Create account",
|
||||
"username": {
|
||||
"label": "Username"
|
||||
},
|
||||
"email": {
|
||||
"label": "E-Mail"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"title": "Second step",
|
||||
"text": "Password",
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"requirement": "Includes at least 6 characters"
|
||||
}
|
||||
},
|
||||
"finish": {
|
||||
"title": "Final step",
|
||||
"text": "Save to database",
|
||||
"card": {
|
||||
"title": "Review your inputs",
|
||||
"text": "After you submit your data to the database, the user will be able to log in. Are you sure that you want to store this user in the database and activate the login?"
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"property": "Property",
|
||||
"value": "Value",
|
||||
"username": "Username",
|
||||
"email": "E-Mail",
|
||||
"password": "Password"
|
||||
},
|
||||
"notSet": "Not set",
|
||||
"valid": "Valid"
|
||||
},
|
||||
"alertConfirmed": "User has been created in the database. They can now log in."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"confirm": "Confirm",
|
||||
"generateRandomPw": "Generate random",
|
||||
"createAnother": "Create another",
|
||||
"goBack": "Go back to users"
|
||||
}
|
||||
}
|
||||
21
public/locales/en/user/invites.json
Normal file
21
public/locales/en/user/invites.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "Manage user invites",
|
||||
"text": "Using invites, you can invite users to your Homarr instance. An invitation will only be valid for a certain time-span and can be used once. The expiration must be between 5 minutes and 12 months upon creation.",
|
||||
"button": {
|
||||
"createInvite": "Create invitation",
|
||||
"deleteInvite": "Delete invite"
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"id": "ID",
|
||||
"creator": "Creator",
|
||||
"expires": "Expires",
|
||||
"action": "Actions"
|
||||
},
|
||||
"data": {
|
||||
"expiresAt": "expired {{at}}",
|
||||
"expiresIn": "in {{in}}"
|
||||
}
|
||||
},
|
||||
"noInvites": "There are no invitations yet."
|
||||
}
|
||||
16
public/locales/en/user/manage.json
Normal file
16
public/locales/en/user/manage.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Manage users",
|
||||
"text": "Using users, you have granular control who can access, edit or delete resources on your Homarr instance.",
|
||||
"buttons": {
|
||||
"create": "Create"
|
||||
},
|
||||
"table": {
|
||||
"header": {
|
||||
"user": "User"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"delete": "Delete user {{name}}"
|
||||
},
|
||||
"searchDoesntMatch": "Your search does not match any entries. Please adjust your filter."
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button, Card, Flex, TextInput } from '@mantine/core';
|
||||
import { useForm, zodResolver } from '@mantine/form';
|
||||
import { IconArrowRight, IconAt, IconUser } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface CreateAccountStepProps {
|
||||
@@ -20,11 +21,13 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
||||
validate: zodResolver(createAccountStepValidationSchema),
|
||||
});
|
||||
|
||||
const { t } = useTranslation('user/create');
|
||||
|
||||
return (
|
||||
<Card mih={400}>
|
||||
<TextInput
|
||||
icon={<IconUser size="0.8rem" />}
|
||||
label="Username"
|
||||
label={t('steps.account.username.label')}
|
||||
variant="filled"
|
||||
mb="md"
|
||||
withAsterisk
|
||||
@@ -32,7 +35,7 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
||||
/>
|
||||
<TextInput
|
||||
icon={<IconAt size="0.8rem" />}
|
||||
label="E-Mail"
|
||||
label={t('steps.account.email.label')}
|
||||
variant="filled"
|
||||
mb="md"
|
||||
{...form.getInputProps('eMail')}
|
||||
@@ -51,7 +54,7 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
||||
variant="light"
|
||||
px="xl"
|
||||
>
|
||||
Next
|
||||
{t('buttons.next')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { z } from 'zod';
|
||||
import { api } from '~/utils/api';
|
||||
import { passwordSchema } from '~/validations/user';
|
||||
@@ -73,6 +74,8 @@ export const CreateAccountSecurityStep = ({
|
||||
/>
|
||||
));
|
||||
|
||||
const { t } = useTranslation('user/create');
|
||||
|
||||
const strength = getStrength(form.values.password);
|
||||
const color = strength === 100 ? 'teal' : strength > 50 ? 'yellow' : 'red';
|
||||
|
||||
@@ -95,7 +98,7 @@ export const CreateAccountSecurityStep = ({
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
label="Password"
|
||||
label={t('steps.security.password.label')}
|
||||
variant="filled"
|
||||
mb="md"
|
||||
withAsterisk
|
||||
@@ -111,7 +114,7 @@ export const CreateAccountSecurityStep = ({
|
||||
variant="default"
|
||||
mt="xl"
|
||||
>
|
||||
Generate random
|
||||
{t('buttons.generateRandomPw')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
@@ -119,7 +122,7 @@ export const CreateAccountSecurityStep = ({
|
||||
<Popover.Dropdown>
|
||||
<Progress color={color} value={strength} size={5} mb="xs" />
|
||||
<PasswordRequirement
|
||||
label="Includes at least 6 characters"
|
||||
label={t('steps.security.password.requirement')}
|
||||
meets={form.values.password.length > 5}
|
||||
/>
|
||||
{checks}
|
||||
@@ -128,7 +131,7 @@ export const CreateAccountSecurityStep = ({
|
||||
|
||||
<Group position="apart" noWrap>
|
||||
<Button leftIcon={<IconArrowLeft size="1rem" />} onClick={prevStep} variant="light" px="xl">
|
||||
Previous
|
||||
{t('buttons.previous')}
|
||||
</Button>
|
||||
<Button
|
||||
rightIcon={<IconArrowRight size="1rem" />}
|
||||
@@ -141,7 +144,7 @@ export const CreateAccountSecurityStep = ({
|
||||
px="xl"
|
||||
disabled={!form.isValid()}
|
||||
>
|
||||
Next
|
||||
{t('buttons.next')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Alert, Button, Card, Flex, Group, Stepper, Table, Text, Title } from '@mantine/core';
|
||||
import { Alert, Button, Card, Group, Stepper, Table, Text, Title } from '@mantine/core';
|
||||
import { useForm, zodResolver } from '@mantine/form';
|
||||
import {
|
||||
IconArrowLeft,
|
||||
@@ -14,6 +14,7 @@ import { GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
CreateAccountStep,
|
||||
@@ -27,6 +28,7 @@ import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { api } from '~/utils/api';
|
||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||
|
||||
const CreateNewUserPage = () => {
|
||||
const [active, setActive] = useState(0);
|
||||
@@ -61,6 +63,8 @@ const CreateNewUserPage = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useTranslation('user/create');
|
||||
|
||||
return (
|
||||
<ManageLayout>
|
||||
<Head>
|
||||
@@ -72,8 +76,8 @@ const CreateNewUserPage = () => {
|
||||
allowStepClick={false}
|
||||
allowStepSelect={false}
|
||||
icon={<IconUser />}
|
||||
label="First step"
|
||||
description="Create account"
|
||||
label={t('steps.account.title')}
|
||||
description={t('steps.account.text')}
|
||||
>
|
||||
<CreateAccountStep
|
||||
defaultUsername={form.values.account.username}
|
||||
@@ -88,8 +92,8 @@ const CreateNewUserPage = () => {
|
||||
allowStepClick={false}
|
||||
allowStepSelect={false}
|
||||
icon={<IconKey />}
|
||||
label="Second step"
|
||||
description="Password"
|
||||
label={t('steps.security.title')}
|
||||
description={t('steps.security.text')}
|
||||
>
|
||||
<CreateAccountSecurityStep
|
||||
defaultPassword={form.values.security.password}
|
||||
@@ -104,21 +108,18 @@ const CreateNewUserPage = () => {
|
||||
allowStepClick={false}
|
||||
allowStepSelect={false}
|
||||
icon={<IconMailCheck />}
|
||||
label="Final step"
|
||||
description="Save to database"
|
||||
label={t('steps.finish.title')}
|
||||
description={t('steps.finish.title')}
|
||||
>
|
||||
<Card mih={400}>
|
||||
<Title order={5}>Review your inputs</Title>
|
||||
<Text mb="xl">
|
||||
After you submit your data to the database, the user will be able to log in. Are you
|
||||
sure that you want to store this user in the database and activate the login?
|
||||
</Text>
|
||||
<Title order={5}>{t('steps.finish.card.title')}</Title>
|
||||
<Text mb="xl">{t('steps.finish.card.text')}</Text>
|
||||
|
||||
<Table mb="lg" withBorder highlightOnHover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
<th>{t('steps.finish.table.header.property')}</th>
|
||||
<th>{t('steps.finish.table.header.value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -126,7 +127,7 @@ const CreateNewUserPage = () => {
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<IconUser size="1rem" />
|
||||
<Text>Username</Text>
|
||||
<Text>{t('steps.finish.table.header.username')}</Text>
|
||||
</Group>
|
||||
</td>
|
||||
<td>{form.values.account.username}</td>
|
||||
@@ -135,7 +136,7 @@ const CreateNewUserPage = () => {
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<IconMail size="1rem" />
|
||||
<Text>E-Mail</Text>
|
||||
<Text>{t('steps.finish.table.header.email')}</Text>
|
||||
</Group>
|
||||
</td>
|
||||
<td>
|
||||
@@ -144,7 +145,7 @@ const CreateNewUserPage = () => {
|
||||
) : (
|
||||
<Group spacing="xs">
|
||||
<IconInfoCircle size="1rem" color="orange" />
|
||||
<Text color="orange">Not set</Text>
|
||||
<Text color="orange">{t('steps.finish.table.notSet')}</Text>
|
||||
</Group>
|
||||
)}
|
||||
</td>
|
||||
@@ -153,13 +154,13 @@ const CreateNewUserPage = () => {
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<IconKey size="1rem" />
|
||||
<Text>Password</Text>
|
||||
<Text>{t('steps.finish.table.password')}</Text>
|
||||
</Group>
|
||||
</td>
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<IconCheck size="1rem" color="green" />
|
||||
<Text color="green">Valid</Text>
|
||||
<Text color="green">{t('steps.finish.table.valid')}</Text>
|
||||
</Group>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -173,7 +174,7 @@ const CreateNewUserPage = () => {
|
||||
variant="light"
|
||||
px="xl"
|
||||
>
|
||||
Previous
|
||||
{t('buttons.previous')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
@@ -188,14 +189,14 @@ const CreateNewUserPage = () => {
|
||||
variant="light"
|
||||
px="xl"
|
||||
>
|
||||
Confirm
|
||||
{t('buttons.confirm')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
</Stepper.Step>
|
||||
<Stepper.Completed>
|
||||
<Alert title="User was created" color="green" mb="md">
|
||||
User has been created in the database. They can now log in.
|
||||
{t('steps.finish.alertConfirmed')}
|
||||
</Alert>
|
||||
|
||||
<Group>
|
||||
@@ -207,7 +208,7 @@ const CreateNewUserPage = () => {
|
||||
leftIcon={<IconUserPlus size="1rem" />}
|
||||
variant="default"
|
||||
>
|
||||
Create another
|
||||
{t('buttons.createAnother')}
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
@@ -215,7 +216,7 @@ const CreateNewUserPage = () => {
|
||||
variant="default"
|
||||
href="/manage/users"
|
||||
>
|
||||
Go back to users
|
||||
{t('buttons.goBack')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stepper.Completed>
|
||||
@@ -234,7 +235,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
}
|
||||
|
||||
const translations = await getServerSideTranslations(
|
||||
['common'],
|
||||
manageNamespaces,
|
||||
ctx.locale,
|
||||
undefined,
|
||||
undefined
|
||||
|
||||
@@ -18,6 +18,7 @@ import { GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
@@ -32,17 +33,16 @@ const ManageUsersPage = () => {
|
||||
search: debouncedSearch,
|
||||
});
|
||||
|
||||
const { t } = useTranslation('user/manage');
|
||||
|
||||
return (
|
||||
<ManageLayout>
|
||||
<Head>
|
||||
<title>Users • Homarr</title>
|
||||
</Head>
|
||||
|
||||
<Title mb="md">Manage users</Title>
|
||||
<Text mb="xl">
|
||||
Using users, you have granular control who can access, edit or delete resources on your
|
||||
Homarr instance.
|
||||
</Text>
|
||||
<Title mb="md">{t('title')}</Title>
|
||||
<Text mb="xl">{t('text')}</Text>
|
||||
|
||||
<Flex columnGap={10} justify="end" mb="md">
|
||||
<Autocomplete
|
||||
@@ -61,7 +61,7 @@ const ManageUsersPage = () => {
|
||||
href="/manage/users/create"
|
||||
variant="default"
|
||||
>
|
||||
Create
|
||||
{t('buttons.create')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -70,7 +70,7 @@ const ManageUsersPage = () => {
|
||||
<Table mb="md" withBorder highlightOnHover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>{t('table.header.user')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -87,7 +87,9 @@ const ManageUsersPage = () => {
|
||||
onClick={() => {
|
||||
openContextModal({
|
||||
modal: 'deleteUserModal',
|
||||
title: <Text weight="bold">Delete user {user.name}</Text>,
|
||||
title: (
|
||||
<Text weight="bold">{t('modals.delete', { name: user.name })}</Text>
|
||||
),
|
||||
innerProps: {
|
||||
userId: user.id,
|
||||
username: user.name ?? '',
|
||||
@@ -110,7 +112,7 @@ const ManageUsersPage = () => {
|
||||
<td colSpan={1}>
|
||||
<Box p={15}>
|
||||
<Text>
|
||||
Your search does not match any entries. Please adjust your filter.
|
||||
{t('searchDoesntMatch')}
|
||||
</Text>
|
||||
</Box>
|
||||
</td>
|
||||
|
||||
@@ -14,11 +14,12 @@ import { IconPlus, IconTrash } from '@tabler/icons-react';
|
||||
import dayjs from 'dayjs';
|
||||
import { GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
const ManageUserInvitesPage = () => {
|
||||
@@ -26,7 +27,6 @@ const ManageUserInvitesPage = () => {
|
||||
const { data } = api.invites.all.useQuery({
|
||||
page: activePage,
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
const { classes } = useStyles();
|
||||
|
||||
@@ -38,17 +38,15 @@ const ManageUserInvitesPage = () => {
|
||||
setActivePage((prev) => prev - 1);
|
||||
};
|
||||
|
||||
const { t } = useTranslation('user/invites');
|
||||
|
||||
return (
|
||||
<ManageLayout>
|
||||
<Head>
|
||||
<title>User invites • Homarr</title>
|
||||
</Head>
|
||||
<Title mb="md">Manage user invites</Title>
|
||||
<Text mb="xl">
|
||||
Using invites, you can invite users to your Homarr instance. An invitation will only be
|
||||
valid for a certain time-span and can be used once. The expiration must be between 5 minutes
|
||||
and 12 months upon creation.
|
||||
</Text>
|
||||
<Title mb="md">{t('title')}</Title>
|
||||
<Text mb="xl">{t('text')}</Text>
|
||||
|
||||
<Flex justify="end" mb="md">
|
||||
<Button
|
||||
@@ -62,7 +60,7 @@ const ManageUserInvitesPage = () => {
|
||||
leftIcon={<IconPlus size="1rem" />}
|
||||
variant="default"
|
||||
>
|
||||
Create invitation
|
||||
{t('button.createInvite')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -71,10 +69,10 @@ const ManageUserInvitesPage = () => {
|
||||
<Table mb="md" withBorder highlightOnHover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Creator</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
<th>{t('table.header.id')}</th>
|
||||
<th>{t('table.header.creator')}</th>
|
||||
<th>{t('table.header.expires')}</th>
|
||||
<th>{t('table.header.action')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -88,9 +86,13 @@ const ManageUserInvitesPage = () => {
|
||||
</td>
|
||||
<td className={classes.tableCell}>
|
||||
{dayjs(dayjs()).isAfter(invite.expires) ? (
|
||||
<Text>expired {dayjs(invite.expires).fromNow()}</Text>
|
||||
<Text>
|
||||
{t('table.data.expiresAt', { at: dayjs(invite.expires).fromNow() })}
|
||||
</Text>
|
||||
) : (
|
||||
<Text>in {dayjs(invite.expires).fromNow(true)}</Text>
|
||||
<Text>
|
||||
{t('table.data.expiresIn', { in: dayjs(invite.expires).fromNow(true) })}
|
||||
</Text>
|
||||
)}
|
||||
</td>
|
||||
<td className={classes.tableCell}>
|
||||
@@ -98,7 +100,7 @@ const ManageUserInvitesPage = () => {
|
||||
onClick={() => {
|
||||
modals.openContextModal({
|
||||
modal: 'deleteInviteModal',
|
||||
title: <Text weight="bold">Delete invite</Text>,
|
||||
title: <Text weight="bold">{t('button.deleteInvite')}</Text>,
|
||||
innerProps: {
|
||||
tokenId: invite.id,
|
||||
},
|
||||
@@ -116,7 +118,7 @@ const ManageUserInvitesPage = () => {
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<Center p="md">
|
||||
<Text color="dimmed">There are no invitations yet.</Text>
|
||||
<Text color="dimmed">{t('noInvites')}</Text>
|
||||
</Center>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -164,7 +166,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
}
|
||||
|
||||
const translations = await getServerSideTranslations(
|
||||
['common'],
|
||||
manageNamespaces,
|
||||
ctx.locale,
|
||||
undefined,
|
||||
undefined
|
||||
|
||||
@@ -43,6 +43,9 @@ export const boardNamespaces = [
|
||||
|
||||
export const manageNamespaces = [
|
||||
'user/preferences',
|
||||
'user/manage',
|
||||
'user/invite',
|
||||
'user/create',
|
||||
'boards/manage',
|
||||
'zod',
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user