🌐 Add missing translations

This commit is contained in:
Manuel
2023-08-05 23:06:40 +02:00
parent cdb88ff941
commit 14c366bddd
9 changed files with 161 additions and 60 deletions

View 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"
}
}

View 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."
}

View 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."
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -43,6 +43,9 @@ export const boardNamespaces = [
export const manageNamespaces = [
'user/preferences',
'user/manage',
'user/invite',
'user/create',
'boards/manage',
'zod',
];