diff --git a/public/locales/en/user/create.json b/public/locales/en/user/create.json new file mode 100644 index 000000000..26ab9546e --- /dev/null +++ b/public/locales/en/user/create.json @@ -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" + } +} \ No newline at end of file diff --git a/public/locales/en/user/invites.json b/public/locales/en/user/invites.json new file mode 100644 index 000000000..9b1183c3d --- /dev/null +++ b/public/locales/en/user/invites.json @@ -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." +} \ No newline at end of file diff --git a/public/locales/en/user/manage.json b/public/locales/en/user/manage.json new file mode 100644 index 000000000..3d1d4e92c --- /dev/null +++ b/public/locales/en/user/manage.json @@ -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." +} \ No newline at end of file diff --git a/src/components/Manage/User/Create/create-account-step.tsx b/src/components/Manage/User/Create/create-account-step.tsx index d2fda30c6..fdd7ab1cb 100644 --- a/src/components/Manage/User/Create/create-account-step.tsx +++ b/src/components/Manage/User/Create/create-account-step.tsx @@ -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 ( } - label="Username" + label={t('steps.account.username.label')} variant="filled" mb="md" withAsterisk @@ -32,7 +35,7 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C /> } - 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')} diff --git a/src/components/Manage/User/Create/security-step.tsx b/src/components/Manage/User/Create/security-step.tsx index 17143faa5..c3a1359df 100644 --- a/src/components/Manage/User/Create/security-step.tsx +++ b/src/components/Manage/User/Create/security-step.tsx @@ -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')} @@ -119,7 +122,7 @@ export const CreateAccountSecurityStep = ({ 5} /> {checks} @@ -128,7 +131,7 @@ export const CreateAccountSecurityStep = ({ diff --git a/src/pages/manage/users/create.tsx b/src/pages/manage/users/create.tsx index 93c5de48a..7ab509c18 100644 --- a/src/pages/manage/users/create.tsx +++ b/src/pages/manage/users/create.tsx @@ -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 ( @@ -72,8 +76,8 @@ const CreateNewUserPage = () => { allowStepClick={false} allowStepSelect={false} icon={} - label="First step" - description="Create account" + label={t('steps.account.title')} + description={t('steps.account.text')} > { allowStepClick={false} allowStepSelect={false} icon={} - label="Second step" - description="Password" + label={t('steps.security.title')} + description={t('steps.security.text')} > { allowStepClick={false} allowStepSelect={false} icon={} - label="Final step" - description="Save to database" + label={t('steps.finish.title')} + description={t('steps.finish.title')} > - Review your inputs - - 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? - + {t('steps.finish.card.title')} + {t('steps.finish.card.text')} - - + + @@ -126,7 +127,7 @@ const CreateNewUserPage = () => { @@ -135,7 +136,7 @@ const CreateNewUserPage = () => { @@ -153,13 +154,13 @@ const CreateNewUserPage = () => { @@ -173,7 +174,7 @@ const CreateNewUserPage = () => { variant="light" px="xl" > - Previous + {t('buttons.previous')} - User has been created in the database. They can now log in. + {t('steps.finish.alertConfirmed')} @@ -207,7 +208,7 @@ const CreateNewUserPage = () => { leftIcon={} variant="default" > - Create another + {t('buttons.createAnother')} @@ -234,7 +235,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { } const translations = await getServerSideTranslations( - ['common'], + manageNamespaces, ctx.locale, undefined, undefined diff --git a/src/pages/manage/users/index.tsx b/src/pages/manage/users/index.tsx index 73715b966..db9041532 100644 --- a/src/pages/manage/users/index.tsx +++ b/src/pages/manage/users/index.tsx @@ -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 ( Users • Homarr - Manage users - - Using users, you have granular control who can access, edit or delete resources on your - Homarr instance. - + {t('title')} + {t('text')} { href="/manage/users/create" variant="default" > - Create + {t('buttons.create')} @@ -70,7 +70,7 @@ const ManageUsersPage = () => {
PropertyValue{t('steps.finish.table.header.property')}{t('steps.finish.table.header.value')}
- Username + {t('steps.finish.table.header.username')} {form.values.account.username} - E-Mail + {t('steps.finish.table.header.email')} @@ -144,7 +145,7 @@ const CreateNewUserPage = () => { ) : ( - Not set + {t('steps.finish.table.notSet')} )} - Password + {t('steps.finish.table.password')} - Valid + {t('steps.finish.table.valid')}
- + @@ -87,7 +87,9 @@ const ManageUsersPage = () => { onClick={() => { openContextModal({ modal: 'deleteUserModal', - title: Delete user {user.name}, + title: ( + {t('modals.delete', { name: user.name })} + ), innerProps: { userId: user.id, username: user.name ?? '', @@ -110,7 +112,7 @@ const ManageUsersPage = () => { diff --git a/src/pages/manage/users/invites.tsx b/src/pages/manage/users/invites.tsx index 4f9974f3e..8432e060e 100644 --- a/src/pages/manage/users/invites.tsx +++ b/src/pages/manage/users/invites.tsx @@ -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 ( User invites • Homarr - Manage user invites - - 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. - + {t('title')} + {t('text')} @@ -71,10 +69,10 @@ const ManageUserInvitesPage = () => {
User{t('table.header.user')}
- Your search does not match any entries. Please adjust your filter. + {t('searchDoesntMatch')}
- - - - + + + + @@ -88,9 +86,13 @@ const ManageUserInvitesPage = () => { @@ -164,7 +166,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { } const translations = await getServerSideTranslations( - ['common'], + manageNamespaces, ctx.locale, undefined, undefined diff --git a/src/tools/server/translation-namespaces.ts b/src/tools/server/translation-namespaces.ts index 3e8e14ba5..6389edd7c 100644 --- a/src/tools/server/translation-namespaces.ts +++ b/src/tools/server/translation-namespaces.ts @@ -43,6 +43,9 @@ export const boardNamespaces = [ export const manageNamespaces = [ 'user/preferences', + 'user/manage', + 'user/invite', + 'user/create', 'boards/manage', 'zod', ];
IDCreatorExpiresActions{t('table.header.id')}{t('table.header.creator')}{t('table.header.expires')}{t('table.header.action')}
{dayjs(dayjs()).isAfter(invite.expires) ? ( - expired {dayjs(invite.expires).fromNow()} + + {t('table.data.expiresAt', { at: dayjs(invite.expires).fromNow() })} + ) : ( - in {dayjs(invite.expires).fromNow(true)} + + {t('table.data.expiresIn', { in: dayjs(invite.expires).fromNow(true) })} + )} @@ -98,7 +100,7 @@ const ManageUserInvitesPage = () => { onClick={() => { modals.openContextModal({ modal: 'deleteInviteModal', - title: Delete invite, + title: {t('button.deleteInvite')}, innerProps: { tokenId: invite.id, }, @@ -116,7 +118,7 @@ const ManageUserInvitesPage = () => {
- There are no invitations yet. + {t('noInvites')}