feat: use existing translation on all pages

This commit is contained in:
Sonny
2024-05-17 19:36:53 +02:00
committed by Sonny
parent 8077f8f9c9
commit 8176817030
19 changed files with 197 additions and 104 deletions

View File

@@ -1,4 +1,7 @@
import { PREFER_DARK_THEME } from '#constants/session'; import {
DARK_THEME_DEFAULT_VALUE,
PREFER_DARK_THEME,
} from '#constants/session';
import { defineConfig } from '@adonisjs/inertia'; import { defineConfig } from '@adonisjs/inertia';
export default defineConfig({ export default defineConfig({
@@ -12,7 +15,8 @@ export default defineConfig({
*/ */
sharedData: { sharedData: {
errors: (ctx) => ctx.session?.flashMessages.get('errors'), errors: (ctx) => ctx.session?.flashMessages.get('errors'),
preferDarkTheme: (ctx) => ctx.session?.get(PREFER_DARK_THEME, true), preferDarkTheme: (ctx) =>
ctx.session?.get(PREFER_DARK_THEME, DARK_THEME_DEFAULT_VALUE),
auth: async (ctx) => { auth: async (ctx) => {
await ctx.auth?.check(); await ctx.auth?.check();
return { return {

View File

@@ -1,5 +1,6 @@
import type Collection from '#models/collection'; import type Collection from '#models/collection';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next';
import { BsThreeDotsVertical } from 'react-icons/bs'; import { BsThreeDotsVertical } from 'react-icons/bs';
import { GoPencil } from 'react-icons/go'; import { GoPencil } from 'react-icons/go';
import { IoIosAddCircleOutline } from 'react-icons/io'; import { IoIosAddCircleOutline } from 'react-icons/io';
@@ -8,30 +9,34 @@ import Dropdown from '~/components/common/dropdown/dropdown';
import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item'; import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item';
import { appendCollectionId } from '~/lib/navigation'; import { appendCollectionId } from '~/lib/navigation';
const CollectionControls = ({ export default function CollectionControls({
collectionId, collectionId,
}: { }: {
collectionId: Collection['id']; collectionId: Collection['id'];
}) => ( }) {
<Dropdown label={<BsThreeDotsVertical />} svgSize={18}> const { t } = useTranslation('common');
<DropdownItemLink href={route('link.create-form').url}> return (
<IoIosAddCircleOutline /> Add <Dropdown label={<BsThreeDotsVertical />} svgSize={18}>
</DropdownItemLink> <DropdownItemLink href={route('link.create-form').url}>
<DropdownItemLink <IoIosAddCircleOutline /> {t('link.create')}
href={appendCollectionId(route('collection.edit-form').url, collectionId)} </DropdownItemLink>
> <DropdownItemLink
<GoPencil /> Edit href={appendCollectionId(
</DropdownItemLink> route('collection.edit-form').url,
<DropdownItemLink collectionId
href={appendCollectionId( )}
route('collection.delete-form').url, >
collectionId <GoPencil /> {t('collection.edit')}
)} </DropdownItemLink>
danger <DropdownItemLink
> href={appendCollectionId(
<IoTrashOutline /> Delete route('collection.delete-form').url,
</DropdownItemLink> collectionId
</Dropdown> )}
); danger
>
export default CollectionControls; <IoTrashOutline /> {t('collection.delete')}
</DropdownItemLink>
</Dropdown>
);
}

View File

@@ -1,4 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import CollectionItem from '~/components/dashboard/collection/list/collection_item'; import CollectionItem from '~/components/dashboard/collection/list/collection_item';
import CollectionListContainer from '~/components/dashboard/collection/list/collection_list_container'; import CollectionListContainer from '~/components/dashboard/collection/list/collection_list_container';
import useActiveCollection from '~/hooks/use_active_collection'; import useActiveCollection from '~/hooks/use_active_collection';
@@ -17,7 +18,10 @@ const SideMenu = styled.nav(({ theme }) => ({
const CollectionLabel = styled.p(({ theme }) => ({ const CollectionLabel = styled.p(({ theme }) => ({
color: theme.colors.grey, color: theme.colors.grey,
marginBlock: '0.35em',
paddingInline: '15px',
})); }));
const CollectionListStyle = styled.div({ const CollectionListStyle = styled.div({
padding: '1px', padding: '1px',
paddingRight: '5px', paddingRight: '5px',
@@ -29,6 +33,7 @@ const CollectionListStyle = styled.div({
}); });
export default function CollectionList() { export default function CollectionList() {
const { t } = useTranslation('common');
const { collections } = useCollections(); const { collections } = useCollections();
const { activeCollection, setActiveCollection } = useActiveCollection(); const { activeCollection, setActiveCollection } = useActiveCollection();
@@ -60,7 +65,10 @@ export default function CollectionList() {
return ( return (
<SideMenu> <SideMenu>
<CollectionListContainer> <CollectionListContainer>
<CollectionLabel>Collections {collections.length}</CollectionLabel> <CollectionLabel>
{t('collection.collections', { count: collections.length })} {' '}
{collections.length}
</CollectionLabel>
<CollectionListStyle> <CollectionListStyle>
{collections.map((collection) => ( {collections.map((collection) => (
<CollectionItem collection={collection} key={collection.id} /> <CollectionItem collection={collection} key={collection.id} />

View File

@@ -3,6 +3,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; import { AiFillStar, AiOutlineStar } from 'react-icons/ai';
import { BsThreeDotsVertical } from 'react-icons/bs'; import { BsThreeDotsVertical } from 'react-icons/bs';
import { GoPencil } from 'react-icons/go'; import { GoPencil } from 'react-icons/go';
@@ -22,6 +23,7 @@ const StartItem = styled(DropdownItemButton)(({ theme }) => ({
export default function LinkControls({ link }: { link: Link }) { export default function LinkControls({ link }: { link: Link }) {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation('common');
const { collections, setCollections } = useCollections(); const { collections, setCollections } = useCollections();
const toggleFavorite = useCallback( const toggleFavorite = useCallback(
@@ -74,11 +76,11 @@ export default function LinkControls({ link }: { link: Link }) {
<StartItem onClick={onFavorite}> <StartItem onClick={onFavorite}>
{!link.favorite ? ( {!link.favorite ? (
<> <>
<AiFillStar /> Add to favorites <AiFillStar /> {t('add-favorite')}
</> </>
) : ( ) : (
<> <>
<AiOutlineStar /> Remove from favorites <AiOutlineStar /> {t('remove-favorite')}
</> </>
)} )}
</StartItem> </StartItem>
@@ -88,7 +90,7 @@ export default function LinkControls({ link }: { link: Link }) {
link.collectionId link.collectionId
)} )}
> >
<GoPencil /> Edit <GoPencil /> {t('link.edit')}
</DropdownItemLink> </DropdownItemLink>
<DropdownItemLink <DropdownItemLink
href={appendCollectionId( href={appendCollectionId(
@@ -97,7 +99,7 @@ export default function LinkControls({ link }: { link: Link }) {
)} )}
danger danger
> >
<IoTrashOutline /> Delete <IoTrashOutline /> {t('link.delete')}
</DropdownItemLink> </DropdownItemLink>
</Dropdown> </Dropdown>
); );

View File

@@ -1,4 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import TextEllipsis from '~/components/common/text_ellipsis'; import TextEllipsis from '~/components/common/text_ellipsis';
import LinkFavicon from '~/components/dashboard/link/link_favicon'; import LinkFavicon from '~/components/dashboard/link/link_favicon';
import FavoriteListContainer from '~/components/dashboard/side_nav/favorite/favorite_container'; import FavoriteListContainer from '~/components/dashboard/side_nav/favorite/favorite_container';
@@ -7,13 +8,18 @@ import useFavorites from '~/hooks/use_favorites';
const FavoriteLabel = styled.p(({ theme }) => ({ const FavoriteLabel = styled.p(({ theme }) => ({
color: theme.colors.grey, color: theme.colors.grey,
marginBlock: '0.35em',
paddingInline: '15px',
})); }));
const NoFavorite = () => ( const NoFavorite = () => {
<FavoriteLabel css={{ textAlign: 'center' }}> const { t } = useTranslation('common');
Your favorites will appear here return (
</FavoriteLabel> <FavoriteLabel css={{ textAlign: 'center' }}>
); {t('favorites-appears-here')}
</FavoriteLabel>
);
};
const FavoriteListStyle = styled.div({ const FavoriteListStyle = styled.div({
padding: '1px', padding: '1px',
@@ -26,6 +32,7 @@ const FavoriteListStyle = styled.div({
}); });
export default function FavoriteList() { export default function FavoriteList() {
const { t } = useTranslation('common');
const { favorites } = useFavorites(); const { favorites } = useFavorites();
if (favorites.length === 0) { if (favorites.length === 0) {
return <NoFavorite key="no-favorite" />; return <NoFavorite key="no-favorite" />;
@@ -33,8 +40,8 @@ export default function FavoriteList() {
return ( return (
<FavoriteListContainer> <FavoriteListContainer>
<FavoriteLabel css={{ marginBlock: '0.35em', paddingInline: '15px' }}> <FavoriteLabel>
Favorites {favorites.length} {t('favorite')} {favorites.length}
</FavoriteLabel> </FavoriteLabel>
<FavoriteListStyle> <FavoriteListStyle>
{favorites.map(({ id, name, url }) => ( {favorites.map(({ id, name, url }) => (

View File

@@ -1,5 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next';
import { AiOutlineFolderAdd } from 'react-icons/ai'; import { AiOutlineFolderAdd } from 'react-icons/ai';
import { IoAdd } from 'react-icons/io5'; import { IoAdd } from 'react-icons/io5';
import { MdOutlineAdminPanelSettings } from 'react-icons/md'; import { MdOutlineAdminPanelSettings } from 'react-icons/md';
@@ -30,13 +31,14 @@ const AddButton = styled(ItemLink)(({ theme }) => ({
})); }));
export default function SideNavigation() { export default function SideNavigation() {
const { t } = useTranslation('common');
const { activeCollection } = useActiveCollection(); const { activeCollection } = useActiveCollection();
return ( return (
<SideMenu> <SideMenu>
<div css={{ paddingInline: '10px' }}> <div css={{ paddingInline: '10px' }}>
<UserCard /> <UserCard />
<AdminButton> <AdminButton>
<MdOutlineAdminPanelSettings /> Administrator <MdOutlineAdminPanelSettings /> {t('admin')}
</AdminButton> </AdminButton>
<ModalSettings openItem={SettingsButton} /> <ModalSettings openItem={SettingsButton} />
<AddButton <AddButton
@@ -45,10 +47,10 @@ export default function SideNavigation() {
activeCollection?.id activeCollection?.id
)} )}
> >
<IoAdd /> Create link <IoAdd /> {t('link.create')}
</AddButton> </AddButton>
<AddButton href={route('collection.create-form').url}> <AddButton href={route('collection.create-form').url}>
<AiOutlineFolderAdd /> Create collection <AiOutlineFolderAdd /> {t('collection.create')}
</AddButton> </AddButton>
</div> </div>
<FavoriteList /> <FavoriteList />

View File

@@ -4,6 +4,7 @@ import TextBox from '~/components/common/form/textbox';
import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
import FormLayout from '~/components/layouts/form_layout'; import FormLayout from '~/components/layouts/form_layout';
import { Visibility } from '../../../app/enums/visibility'; import { Visibility } from '../../../app/enums/visibility';
import { useTranslation } from 'react-i18next';
export type FormCollectionData = { export type FormCollectionData = {
name: string; name: string;
@@ -30,6 +31,7 @@ export default function FormCollection({
setData: (name: string, value: string) => void; setData: (name: string, value: string) => void;
handleSubmit: () => void; handleSubmit: () => void;
}) { }) {
const { t } = useTranslation('common');
const handleOnCheck = ({ target }: ChangeEvent<HTMLInputElement>) => const handleOnCheck = ({ target }: ChangeEvent<HTMLInputElement>) =>
setData( setData(
'visibility', 'visibility',
@@ -50,8 +52,8 @@ export default function FormCollection({
> >
<BackToDashboard> <BackToDashboard>
<TextBox <TextBox
label="Collection name" label={t('collection.name')}
placeholder="Collection name" placeholder={t('collection.name')}
name="name" name="name"
onChange={setData} onChange={setData}
value={data.name} value={data.name}
@@ -60,8 +62,8 @@ export default function FormCollection({
autoFocus autoFocus
/> />
<TextBox <TextBox
label="Collection description" label={t('collection.name')}
placeholder="Collection description" placeholder={t('collection.name')}
name="description" name="description"
onChange={setData} onChange={setData}
value={data.description ?? undefined} value={data.description ?? undefined}

View File

@@ -0,0 +1,28 @@
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { LS_LANG_KEY } from '~/constants';
import { languages } from '~/i18n';
export default function LangSelector() {
const { t, i18n } = useTranslation('common');
const onToggleLanguageClick = ({
target,
}: ChangeEvent<HTMLSelectElement>) => {
i18n.changeLanguage(target.value);
localStorage.setItem(LS_LANG_KEY, target.value);
};
return (
<select
onChange={onToggleLanguageClick}
name="lang-selector"
id="lang-selector"
defaultValue={i18n.language}
>
{languages.map((lang) => (
<option value={lang}>{t(`language.${lang}`)}</option>
))}
</select>
);
}

View File

@@ -1,13 +1,16 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from '@inertiajs/react'; import { Head, Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { FormEvent, ReactNode } from 'react'; import { FormEvent, ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '~/components/common/form/_button'; import Button from '~/components/common/form/_button';
import Form from '~/components/common/form/_form'; import Form from '~/components/common/form/_form';
import TransitionLayout from '~/components/layouts/_transition_layout';
import i18n from '~/i18n';
import { appendCollectionId } from '~/lib/navigation'; import { appendCollectionId } from '~/lib/navigation';
import BaseLayout from './_base_layout'; import BaseLayout from './_base_layout';
const FormLayoutStyle = styled.div(({ theme }) => ({ const FormLayoutStyle = styled(TransitionLayout)(({ theme }) => ({
height: 'fit-content', height: 'fit-content',
width: theme.media.mobile, width: theme.media.mobile,
maxWidth: '100%', maxWidth: '100%',
@@ -30,31 +33,33 @@ interface FormLayoutProps {
collectionId?: string; collectionId?: string;
} }
const FormLayout = ({ export default function FormLayout({
title, title,
children, children,
canSubmit, canSubmit,
handleSubmit, handleSubmit,
textSubmitButton = 'Confirm', textSubmitButton = i18n.t('common:confirm'),
disableHomeLink = false, disableHomeLink = false,
collectionId, collectionId,
}: FormLayoutProps) => ( }: FormLayoutProps) {
<BaseLayout> const { t } = useTranslation('common');
<FormLayoutStyle> return (
<h2>{title}</h2> <BaseLayout>
<Form onSubmit={handleSubmit}> <FormLayoutStyle>
{children} <Head title={title} />
<Button type="submit" disabled={!canSubmit}> <h2>{title}</h2>
{textSubmitButton} <Form onSubmit={handleSubmit}>
</Button> {children}
</Form> <Button type="submit" disabled={!canSubmit}>
{!disableHomeLink && ( {textSubmitButton}
<Link href={appendCollectionId(route('dashboard').url, collectionId)}> </Button>
Back to home </Form>
</Link> {!disableHomeLink && (
)} <Link href={appendCollectionId(route('dashboard').url, collectionId)}>
</FormLayoutStyle> {t('back-home')}
</BaseLayout> </Link>
); )}
</FormLayoutStyle>
export default FormLayout; </BaseLayout>
);
}

View File

@@ -1,6 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from '@inertiajs/react'; import { Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client'; import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next';
import { IoIosLogOut } from 'react-icons/io'; import { IoIosLogOut } from 'react-icons/io';
import Dropdown from '~/components/common/dropdown/dropdown'; import Dropdown from '~/components/common/dropdown/dropdown';
import { import {
@@ -49,7 +50,9 @@ const UserCard = styled.div({
}); });
export default function Navbar() { export default function Navbar() {
const { t } = useTranslation('common');
const { isAuthenticated, user } = useUser(); const { isAuthenticated, user } = useUser();
return ( return (
<Nav> <Nav>
<NavList> <NavList>
@@ -73,9 +76,14 @@ export default function Navbar() {
</li> </li>
</> </>
) : ( ) : (
<li> <>
<Link href={route('auth.login').url}>Login</Link> <li>
</li> <ModalSettings openItem={DropdownItemButton} />
</li>
<li>
<Link href={route('auth.login').url}>{t('login')}</Link>
</li>
</>
)} )}
</NavList> </NavList>
</Nav> </Nav>
@@ -83,7 +91,9 @@ export default function Navbar() {
} }
function ProfileDropdown() { function ProfileDropdown() {
const { t } = useTranslation('common');
const { user } = useUser(); const { user } = useUser();
return ( return (
<Dropdown <Dropdown
label={ label={
@@ -99,7 +109,7 @@ function ProfileDropdown() {
> >
<ModalSettings openItem={DropdownItemButton} /> <ModalSettings openItem={DropdownItemButton} />
<DropdownItemLink href={route('auth.logout').url} danger> <DropdownItemLink href={route('auth.logout').url} danger>
<IoIosLogOut /> Logout <IoIosLogOut /> {t('logout')}
</DropdownItemLink> </DropdownItemLink>
</Dropdown> </Dropdown>
); );

View File

@@ -1,5 +1,7 @@
import { useTranslation } from 'react-i18next';
import { BsGear } from 'react-icons/bs'; import { BsGear } from 'react-icons/bs';
import Modal from '~/components/common/modal/modal'; import Modal from '~/components/common/modal/modal';
import LangSelector from '~/components/lang_selector';
import ThemeSwitcher from '~/components/theme_switcher'; import ThemeSwitcher from '~/components/theme_switcher';
import useToggle from '~/hooks/use_modal'; import useToggle from '~/hooks/use_modal';
@@ -9,19 +11,16 @@ export default function ModalSettings({
// TODO: fix this :() // TODO: fix this :()
openItem: any; openItem: any;
}) { }) {
const { t } = useTranslation('common');
const { isShowing, open, close } = useToggle(); const { isShowing, open, close } = useToggle();
return ( return (
<> <>
<OpenItem onClick={open}> <OpenItem onClick={open}>
<BsGear /> <BsGear />
Settings {t('settings')}
</OpenItem> </OpenItem>
<Modal title="Settings" opened={isShowing} close={close}> <Modal title={t('settings')} opened={isShowing} close={close}>
Modal settings <LangSelector />
<hr />
<select>
<option>EN</option>
</select>
<hr /> <hr />
<ThemeSwitcher /> <ThemeSwitcher />
</Modal> </Modal>

View File

@@ -0,0 +1 @@
export const LS_LANG_KEY = 'language';

View File

@@ -12,6 +12,7 @@ import frResourceLogin from './locales/fr/login.json';
import frResourcePrivacy from './locales/fr/privacy.json'; import frResourcePrivacy from './locales/fr/privacy.json';
import frResourceTerms from './locales/fr/terms.json'; import frResourceTerms from './locales/fr/terms.json';
import { LS_LANG_KEY } from '~/constants';
import enResourceAbout from './locales/en/about.json'; import enResourceAbout from './locales/en/about.json';
import enResourceAdmin from './locales/en/admin.json'; import enResourceAdmin from './locales/en/admin.json';
import enResourceCommon from './locales/en/common.json'; import enResourceCommon from './locales/en/common.json';
@@ -51,9 +52,16 @@ export const resources = {
}, },
} as const; } as const;
export const languages = ['en', 'fr'] as const;
const lng =
typeof window !== 'undefined'
? localStorage.getItem(LS_LANG_KEY) || undefined
: undefined;
i18n.use(initReactI18next).init({ i18n.use(initReactI18next).init({
returnNull: false, returnNull: false,
resources, resources,
lng,
fallbackLng: 'en', fallbackLng: 'en',
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,

View File

@@ -2,7 +2,7 @@
"slogan": "Manage your links in the best possible way", "slogan": "Manage your links in the best possible way",
"confirm": "Confirm", "confirm": "Confirm",
"cancel": "Cancel", "cancel": "Cancel",
"back-home": "← Back to home page", "back-home": "← Back to home",
"logout": "Logout", "logout": "Logout",
"login": "Login", "login": "Login",
"link": { "link": {
@@ -12,25 +12,29 @@
"description": "Link description", "description": "Link description",
"create": "Create a link", "create": "Create a link",
"edit": "Edit a link", "edit": "Edit a link",
"remove": "Delete a link", "delete": "Delete a link",
"remove-confirm": "Confirm deletion?" "delete-confirm": "Confirm deletion?"
}, },
"collection": { "collection": {
"collections": "Collections", "collections": "Collection",
"collection": "Collection", "collections_other": "Collections",
"name": "Collection name", "name": "Collection name",
"description": "Collection description", "description": "Collection description",
"no-description": "No description", "no-description": "No description",
"visibility": "Public", "visibility": "Public",
"create": "Create a collection", "create": "Create a collection",
"edit": "Edit a collection", "edit": "Edit a collection",
"remove": "Delete a collection", "delete": "Delete a collection",
"remove-confirm": "Confirm deletion?", "delete-confirm": "Confirm deletion?",
"remove-description": "You must delete all links in this collection before you can delete this collection." "delete-description": "You must delete all links in this collection before you can delete this collection."
}, },
"favorite": "Favorite", "favorite": "Favorite",
"add-favorite": "Add to favorites",
"remove-favorite": "Remove from favorites",
"favorites-appears-here": "Your favorites will appear here",
"no-item-found": "No item found", "no-item-found": "No item found",
"search": "Search", "search": "Search",
"admin": "Administrator",
"avatar": "{{name}}'s avatar", "avatar": "{{name}}'s avatar",
"generic-error": "Something went wrong", "generic-error": "Something went wrong",
"generic-error-description": "An error has occurred, if this happens again please <a href=\"https://github.com/Sonny93/my-links\" target=\"_blank\">create an issue</a> with as much detail as possible.", "generic-error-description": "An error has occurred, if this happens again please <a href=\"https://github.com/Sonny93/my-links\" target=\"_blank\">create an issue</a> with as much detail as possible.",

View File

@@ -12,8 +12,8 @@
"description": "Description du lien", "description": "Description du lien",
"create": "Créer un lien", "create": "Créer un lien",
"edit": "Modifier un lien", "edit": "Modifier un lien",
"remove": "Supprimer un lien", "delete": "Supprimer un lien",
"remove-confirm": "Confirmer la suppression ?" "delete-confirm": "Confirmer la suppression ?"
}, },
"collection": { "collection": {
"collections": "Collection", "collections": "Collection",
@@ -25,12 +25,16 @@
"no-description": "Aucune description", "no-description": "Aucune description",
"create": "Créer une collection", "create": "Créer une collection",
"edit": "Modifier une collection", "edit": "Modifier une collection",
"remove": "Supprimer une collection", "delete": "Supprimer une collection",
"remove-confirm": "Confirmer la suppression ?", "delete-confirm": "Confirmer la suppression ?",
"remove-description": "Vous devez supprimer tous les liens de cette collection avant de pouvoir supprimer cette collection" "delete-description": "Vous devez supprimer tous les liens de cette collection avant de pouvoir supprimer cette collection"
}, },
"favorite": "Favoris", "favorite": "Favoris",
"add-favorite": "Ajouter aux favoris",
"remove-favorite": "Retirer des favoris",
"favorites-appears-here": "Vos favoris apparaîtront ici",
"no-item-found": "Aucun élément trouvé", "no-item-found": "Aucun élément trouvé",
"admin": "Administrateur",
"search": "Rechercher", "search": "Rechercher",
"avatar": "Avatar de {{name}}", "avatar": "Avatar de {{name}}",
"generic-error": "Une erreur est survenue", "generic-error": "Une erreur est survenue",

View File

@@ -4,12 +4,14 @@ import FormCollection, {
FormCollectionData, FormCollectionData,
} from '~/components/form/form_collection'; } from '~/components/form/form_collection';
import { Visibility } from '../../../app/enums/visibility'; import { Visibility } from '../../../app/enums/visibility';
import { useTranslation } from 'react-i18next';
export default function CreateCollectionPage({ export default function CreateCollectionPage({
disableHomeLink, disableHomeLink,
}: { }: {
disableHomeLink: boolean; disableHomeLink: boolean;
}) { }) {
const { t } = useTranslation('common');
const { data, setData, post, processing } = useForm<FormCollectionData>({ const { data, setData, post, processing } = useForm<FormCollectionData>({
name: '', name: '',
description: '', description: '',
@@ -24,7 +26,7 @@ export default function CreateCollectionPage({
return ( return (
<FormCollection <FormCollection
title="Create a collection" title={t('collection.create')}
canSubmit={!isFormDisabled} canSubmit={!isFormDisabled}
disableHomeLink={disableHomeLink} disableHomeLink={disableHomeLink}
data={data} data={data}

View File

@@ -1,6 +1,7 @@
import type Collection from '#models/collection'; import type Collection from '#models/collection';
import { useForm } from '@inertiajs/react'; import { useForm } from '@inertiajs/react';
import { ChangeEvent, FormEvent, useMemo } from 'react'; import { ChangeEvent, FormEvent, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import FormField from '~/components/common/form/_form_field'; import FormField from '~/components/common/form/_form_field';
import TextBox from '~/components/common/form/textbox'; import TextBox from '~/components/common/form/textbox';
import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
@@ -13,6 +14,7 @@ export default function CreateLinkPage({
}: { }: {
collections: Collection[]; collections: Collection[];
}) { }) {
const { t } = useTranslation('common');
const collectionId = useSearchParam('collectionId') ?? collections[0].id; const collectionId = useSearchParam('collectionId') ?? collections[0].id;
const { data, setData, post, processing } = useForm({ const { data, setData, post, processing } = useForm({
name: '', name: '',
@@ -50,8 +52,8 @@ export default function CreateLinkPage({
> >
<BackToDashboard> <BackToDashboard>
<TextBox <TextBox
label="Link name" label={t('link.name')}
placeholder="Link name" placeholder={t('link.name')}
name="name" name="name"
onChange={setData} onChange={setData}
value={data.name} value={data.name}
@@ -67,8 +69,8 @@ export default function CreateLinkPage({
required required
/> />
<TextBox <TextBox
label="Link description" label={t('link.description')}
placeholder="Link description" placeholder={t('link.description')}
name="description" name="description"
onChange={setData} onChange={setData}
value={data.description} value={data.description}
@@ -84,7 +86,7 @@ export default function CreateLinkPage({
))} ))}
</select> </select>
<FormField required> <FormField required>
<label htmlFor="favorite">Favorite</label> <label htmlFor="favorite">{t('favorite')}</label>
<input <input
type="checkbox" type="checkbox"
onChange={handleOnCheck} onChange={handleOnCheck}

8
package-lock.json generated
View File

@@ -26,7 +26,7 @@
"@vinejs/vine": "^2.0.0", "@vinejs/vine": "^2.0.0",
"edge.js": "^6.0.2", "edge.js": "^6.0.2",
"hex-rgb": "^5.0.0", "hex-rgb": "^5.0.0",
"i18next": "^23.11.3", "i18next": "^23.11.4",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"node-html-parser": "^6.1.13", "node-html-parser": "^6.1.13",
"pg": "^8.11.5", "pg": "^8.11.5",
@@ -6539,9 +6539,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "23.11.3", "version": "23.11.4",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.3.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.4.tgz",
"integrity": "sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==", "integrity": "sha512-CCUjtd5TfaCl+mLUzAA0uPSN+AVn4fP/kWCYt/hocPUwusTpMVczdrRyOBUwk6N05iH40qiKx6q1DoNJtBIwdg==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",

View File

@@ -78,7 +78,7 @@
"@vinejs/vine": "^2.0.0", "@vinejs/vine": "^2.0.0",
"edge.js": "^6.0.2", "edge.js": "^6.0.2",
"hex-rgb": "^5.0.0", "hex-rgb": "^5.0.0",
"i18next": "^23.11.3", "i18next": "^23.11.4",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"node-html-parser": "^6.1.13", "node-html-parser": "^6.1.13",
"pg": "^8.11.5", "pg": "^8.11.5",