diff --git a/config/inertia.ts b/config/inertia.ts
index 357ec89..992f43d 100644
--- a/config/inertia.ts
+++ b/config/inertia.ts
@@ -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';
export default defineConfig({
@@ -12,7 +15,8 @@ export default defineConfig({
*/
sharedData: {
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) => {
await ctx.auth?.check();
return {
diff --git a/inertia/components/dashboard/collection/header/collection_controls.tsx b/inertia/components/dashboard/collection/header/collection_controls.tsx
index 7aec8ab..3abed74 100644
--- a/inertia/components/dashboard/collection/header/collection_controls.tsx
+++ b/inertia/components/dashboard/collection/header/collection_controls.tsx
@@ -1,5 +1,6 @@
import type Collection from '#models/collection';
import { route } from '@izzyjs/route/client';
+import { useTranslation } from 'react-i18next';
import { BsThreeDotsVertical } from 'react-icons/bs';
import { GoPencil } from 'react-icons/go';
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 { appendCollectionId } from '~/lib/navigation';
-const CollectionControls = ({
+export default function CollectionControls({
collectionId,
}: {
collectionId: Collection['id'];
-}) => (
- } svgSize={18}>
-
- Add
-
-
- Edit
-
-
- Delete
-
-
-);
-
-export default CollectionControls;
+}) {
+ const { t } = useTranslation('common');
+ return (
+ } svgSize={18}>
+
+ {t('link.create')}
+
+
+ {t('collection.edit')}
+
+
+ {t('collection.delete')}
+
+
+ );
+}
diff --git a/inertia/components/dashboard/collection/list/collection_list.tsx b/inertia/components/dashboard/collection/list/collection_list.tsx
index c5afb4c..fec5897 100644
--- a/inertia/components/dashboard/collection/list/collection_list.tsx
+++ b/inertia/components/dashboard/collection/list/collection_list.tsx
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
+import { useTranslation } from 'react-i18next';
import CollectionItem from '~/components/dashboard/collection/list/collection_item';
import CollectionListContainer from '~/components/dashboard/collection/list/collection_list_container';
import useActiveCollection from '~/hooks/use_active_collection';
@@ -17,7 +18,10 @@ const SideMenu = styled.nav(({ theme }) => ({
const CollectionLabel = styled.p(({ theme }) => ({
color: theme.colors.grey,
+ marginBlock: '0.35em',
+ paddingInline: '15px',
}));
+
const CollectionListStyle = styled.div({
padding: '1px',
paddingRight: '5px',
@@ -29,6 +33,7 @@ const CollectionListStyle = styled.div({
});
export default function CollectionList() {
+ const { t } = useTranslation('common');
const { collections } = useCollections();
const { activeCollection, setActiveCollection } = useActiveCollection();
@@ -60,7 +65,10 @@ export default function CollectionList() {
return (
- Collections • {collections.length}
+
+ {t('collection.collections', { count: collections.length })} •{' '}
+ {collections.length}
+
{collections.map((collection) => (
diff --git a/inertia/components/dashboard/link/link_controls.tsx b/inertia/components/dashboard/link/link_controls.tsx
index 85ceca6..5b8fa3a 100644
--- a/inertia/components/dashboard/link/link_controls.tsx
+++ b/inertia/components/dashboard/link/link_controls.tsx
@@ -3,6 +3,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { route } from '@izzyjs/route/client';
import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
import { AiFillStar, AiOutlineStar } from 'react-icons/ai';
import { BsThreeDotsVertical } from 'react-icons/bs';
import { GoPencil } from 'react-icons/go';
@@ -22,6 +23,7 @@ const StartItem = styled(DropdownItemButton)(({ theme }) => ({
export default function LinkControls({ link }: { link: Link }) {
const theme = useTheme();
+ const { t } = useTranslation('common');
const { collections, setCollections } = useCollections();
const toggleFavorite = useCallback(
@@ -74,11 +76,11 @@ export default function LinkControls({ link }: { link: Link }) {
{!link.favorite ? (
<>
- Add to favorites
+ {t('add-favorite')}
>
) : (
<>
- Remove from favorites
+ {t('remove-favorite')}
>
)}
@@ -88,7 +90,7 @@ export default function LinkControls({ link }: { link: Link }) {
link.collectionId
)}
>
- Edit
+ {t('link.edit')}
- Delete
+ {t('link.delete')}
);
diff --git a/inertia/components/dashboard/side_nav/favorite/favorite_list.tsx b/inertia/components/dashboard/side_nav/favorite/favorite_list.tsx
index fc34bfa..306b7ed 100644
--- a/inertia/components/dashboard/side_nav/favorite/favorite_list.tsx
+++ b/inertia/components/dashboard/side_nav/favorite/favorite_list.tsx
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
+import { useTranslation } from 'react-i18next';
import TextEllipsis from '~/components/common/text_ellipsis';
import LinkFavicon from '~/components/dashboard/link/link_favicon';
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 }) => ({
color: theme.colors.grey,
+ marginBlock: '0.35em',
+ paddingInline: '15px',
}));
-const NoFavorite = () => (
-
- Your favorites will appear here
-
-);
+const NoFavorite = () => {
+ const { t } = useTranslation('common');
+ return (
+
+ {t('favorites-appears-here')}
+
+ );
+};
const FavoriteListStyle = styled.div({
padding: '1px',
@@ -26,6 +32,7 @@ const FavoriteListStyle = styled.div({
});
export default function FavoriteList() {
+ const { t } = useTranslation('common');
const { favorites } = useFavorites();
if (favorites.length === 0) {
return ;
@@ -33,8 +40,8 @@ export default function FavoriteList() {
return (
-
- Favorites • {favorites.length}
+
+ {t('favorite')} • {favorites.length}
{favorites.map(({ id, name, url }) => (
diff --git a/inertia/components/dashboard/side_nav/side_navigation.tsx b/inertia/components/dashboard/side_nav/side_navigation.tsx
index 16a3b2c..1432f6e 100644
--- a/inertia/components/dashboard/side_nav/side_navigation.tsx
+++ b/inertia/components/dashboard/side_nav/side_navigation.tsx
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
import { route } from '@izzyjs/route/client';
+import { useTranslation } from 'react-i18next';
import { AiOutlineFolderAdd } from 'react-icons/ai';
import { IoAdd } from 'react-icons/io5';
import { MdOutlineAdminPanelSettings } from 'react-icons/md';
@@ -30,13 +31,14 @@ const AddButton = styled(ItemLink)(({ theme }) => ({
}));
export default function SideNavigation() {
+ const { t } = useTranslation('common');
const { activeCollection } = useActiveCollection();
return (
- Administrator
+ {t('admin')}
- Create link
+ {t('link.create')}
- Create collection
+ {t('collection.create')}
diff --git a/inertia/components/form/form_collection.tsx b/inertia/components/form/form_collection.tsx
index 1e1a9d6..1239380 100644
--- a/inertia/components/form/form_collection.tsx
+++ b/inertia/components/form/form_collection.tsx
@@ -4,6 +4,7 @@ import TextBox from '~/components/common/form/textbox';
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
import FormLayout from '~/components/layouts/form_layout';
import { Visibility } from '../../../app/enums/visibility';
+import { useTranslation } from 'react-i18next';
export type FormCollectionData = {
name: string;
@@ -30,6 +31,7 @@ export default function FormCollection({
setData: (name: string, value: string) => void;
handleSubmit: () => void;
}) {
+ const { t } = useTranslation('common');
const handleOnCheck = ({ target }: ChangeEvent) =>
setData(
'visibility',
@@ -50,8 +52,8 @@ export default function FormCollection({
>
) => {
+ i18n.changeLanguage(target.value);
+ localStorage.setItem(LS_LANG_KEY, target.value);
+ };
+
+ return (
+
+ );
+}
diff --git a/inertia/components/layouts/form_layout.tsx b/inertia/components/layouts/form_layout.tsx
index ed4b627..e521474 100644
--- a/inertia/components/layouts/form_layout.tsx
+++ b/inertia/components/layouts/form_layout.tsx
@@ -1,13 +1,16 @@
import styled from '@emotion/styled';
-import { Link } from '@inertiajs/react';
+import { Head, Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { FormEvent, ReactNode } from 'react';
+import { useTranslation } from 'react-i18next';
import Button from '~/components/common/form/_button';
import Form from '~/components/common/form/_form';
+import TransitionLayout from '~/components/layouts/_transition_layout';
+import i18n from '~/i18n';
import { appendCollectionId } from '~/lib/navigation';
import BaseLayout from './_base_layout';
-const FormLayoutStyle = styled.div(({ theme }) => ({
+const FormLayoutStyle = styled(TransitionLayout)(({ theme }) => ({
height: 'fit-content',
width: theme.media.mobile,
maxWidth: '100%',
@@ -30,31 +33,33 @@ interface FormLayoutProps {
collectionId?: string;
}
-const FormLayout = ({
+export default function FormLayout({
title,
children,
canSubmit,
handleSubmit,
- textSubmitButton = 'Confirm',
+ textSubmitButton = i18n.t('common:confirm'),
disableHomeLink = false,
collectionId,
-}: FormLayoutProps) => (
-
-
- {title}
-
- {!disableHomeLink && (
-
- ← Back to home
-
- )}
-
-
-);
-
-export default FormLayout;
+}: FormLayoutProps) {
+ const { t } = useTranslation('common');
+ return (
+
+
+
+ {title}
+
+ {!disableHomeLink && (
+
+ {t('back-home')}
+
+ )}
+
+
+ );
+}
diff --git a/inertia/components/navbar/navbar.tsx b/inertia/components/navbar/navbar.tsx
index 9e99544..5b513be 100644
--- a/inertia/components/navbar/navbar.tsx
+++ b/inertia/components/navbar/navbar.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
+import { useTranslation } from 'react-i18next';
import { IoIosLogOut } from 'react-icons/io';
import Dropdown from '~/components/common/dropdown/dropdown';
import {
@@ -49,7 +50,9 @@ const UserCard = styled.div({
});
export default function Navbar() {
+ const { t } = useTranslation('common');
const { isAuthenticated, user } = useUser();
+
return (
@@ -83,7 +91,9 @@ export default function Navbar() {
}
function ProfileDropdown() {
+ const { t } = useTranslation('common');
const { user } = useUser();
+
return (
- Logout
+ {t('logout')}
);
diff --git a/inertia/components/settings/modal.tsx b/inertia/components/settings/modal.tsx
index 35b2fc7..1a6faef 100644
--- a/inertia/components/settings/modal.tsx
+++ b/inertia/components/settings/modal.tsx
@@ -1,5 +1,7 @@
+import { useTranslation } from 'react-i18next';
import { BsGear } from 'react-icons/bs';
import Modal from '~/components/common/modal/modal';
+import LangSelector from '~/components/lang_selector';
import ThemeSwitcher from '~/components/theme_switcher';
import useToggle from '~/hooks/use_modal';
@@ -9,19 +11,16 @@ export default function ModalSettings({
// TODO: fix this :()
openItem: any;
}) {
+ const { t } = useTranslation('common');
const { isShowing, open, close } = useToggle();
return (
<>
- Settings
+ {t('settings')}
-
- Modal settings
-
-
+
+
diff --git a/inertia/constants/index.ts b/inertia/constants/index.ts
new file mode 100644
index 0000000..2bcd071
--- /dev/null
+++ b/inertia/constants/index.ts
@@ -0,0 +1 @@
+export const LS_LANG_KEY = 'language';
diff --git a/inertia/i18n/index.ts b/inertia/i18n/index.ts
index 966569a..b499bfb 100644
--- a/inertia/i18n/index.ts
+++ b/inertia/i18n/index.ts
@@ -12,6 +12,7 @@ import frResourceLogin from './locales/fr/login.json';
import frResourcePrivacy from './locales/fr/privacy.json';
import frResourceTerms from './locales/fr/terms.json';
+import { LS_LANG_KEY } from '~/constants';
import enResourceAbout from './locales/en/about.json';
import enResourceAdmin from './locales/en/admin.json';
import enResourceCommon from './locales/en/common.json';
@@ -51,9 +52,16 @@ export const resources = {
},
} 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({
returnNull: false,
resources,
+ lng,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
diff --git a/inertia/i18n/locales/en/common.json b/inertia/i18n/locales/en/common.json
index 0195212..aed1f13 100644
--- a/inertia/i18n/locales/en/common.json
+++ b/inertia/i18n/locales/en/common.json
@@ -2,7 +2,7 @@
"slogan": "Manage your links in the best possible way",
"confirm": "Confirm",
"cancel": "Cancel",
- "back-home": "← Back to home page",
+ "back-home": "← Back to home",
"logout": "Logout",
"login": "Login",
"link": {
@@ -12,25 +12,29 @@
"description": "Link description",
"create": "Create a link",
"edit": "Edit a link",
- "remove": "Delete a link",
- "remove-confirm": "Confirm deletion?"
+ "delete": "Delete a link",
+ "delete-confirm": "Confirm deletion?"
},
"collection": {
- "collections": "Collections",
- "collection": "Collection",
+ "collections": "Collection",
+ "collections_other": "Collections",
"name": "Collection name",
"description": "Collection description",
"no-description": "No description",
"visibility": "Public",
"create": "Create a collection",
"edit": "Edit a collection",
- "remove": "Delete a collection",
- "remove-confirm": "Confirm deletion?",
- "remove-description": "You must delete all links in this collection before you can delete this collection."
+ "delete": "Delete a collection",
+ "delete-confirm": "Confirm deletion?",
+ "delete-description": "You must delete all links in this collection before you can delete this collection."
},
"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",
"search": "Search",
+ "admin": "Administrator",
"avatar": "{{name}}'s avatar",
"generic-error": "Something went wrong",
"generic-error-description": "An error has occurred, if this happens again please create an issue with as much detail as possible.",
diff --git a/inertia/i18n/locales/fr/common.json b/inertia/i18n/locales/fr/common.json
index 8819966..cac13d4 100644
--- a/inertia/i18n/locales/fr/common.json
+++ b/inertia/i18n/locales/fr/common.json
@@ -12,8 +12,8 @@
"description": "Description du lien",
"create": "Créer un lien",
"edit": "Modifier un lien",
- "remove": "Supprimer un lien",
- "remove-confirm": "Confirmer la suppression ?"
+ "delete": "Supprimer un lien",
+ "delete-confirm": "Confirmer la suppression ?"
},
"collection": {
"collections": "Collection",
@@ -25,12 +25,16 @@
"no-description": "Aucune description",
"create": "Créer une collection",
"edit": "Modifier une collection",
- "remove": "Supprimer une collection",
- "remove-confirm": "Confirmer la suppression ?",
- "remove-description": "Vous devez supprimer tous les liens de cette collection avant de pouvoir supprimer cette collection"
+ "delete": "Supprimer une collection",
+ "delete-confirm": "Confirmer la suppression ?",
+ "delete-description": "Vous devez supprimer tous les liens de cette collection avant de pouvoir supprimer cette collection"
},
"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é",
+ "admin": "Administrateur",
"search": "Rechercher",
"avatar": "Avatar de {{name}}",
"generic-error": "Une erreur est survenue",
diff --git a/inertia/pages/collections/create.tsx b/inertia/pages/collections/create.tsx
index 6d9341f..e8a126c 100644
--- a/inertia/pages/collections/create.tsx
+++ b/inertia/pages/collections/create.tsx
@@ -4,12 +4,14 @@ import FormCollection, {
FormCollectionData,
} from '~/components/form/form_collection';
import { Visibility } from '../../../app/enums/visibility';
+import { useTranslation } from 'react-i18next';
export default function CreateCollectionPage({
disableHomeLink,
}: {
disableHomeLink: boolean;
}) {
+ const { t } = useTranslation('common');
const { data, setData, post, processing } = useForm({
name: '',
description: '',
@@ -24,7 +26,7 @@ export default function CreateCollectionPage({
return (
-
+