mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 07:03:25 +00:00
feat: add i18n with type safety
This commit is contained in:
@@ -5,15 +5,15 @@ const PATHS = {
|
||||
GOOGLE: '/auth/google',
|
||||
},
|
||||
HOME: '/',
|
||||
APP: '/app',
|
||||
DASHBOARD: '/dashboard',
|
||||
SHARED: '/shared',
|
||||
PRIVACY: '/privacy',
|
||||
TERMS: '/terms',
|
||||
ADMIN: '/admin',
|
||||
CATEGORY: {
|
||||
CREATE: '/category/create',
|
||||
EDIT: '/category/edit',
|
||||
REMOVE: '/category/remove',
|
||||
COLLECTION: {
|
||||
CREATE: '/collections/create',
|
||||
EDIT: '/collections/edit',
|
||||
REMOVE: '/collections/remove',
|
||||
},
|
||||
LINK: {
|
||||
CREATE: '/link/create',
|
||||
@@ -21,14 +21,15 @@ const PATHS = {
|
||||
REMOVE: '/link/remove',
|
||||
},
|
||||
API: {
|
||||
CATEGORY: '/api/category',
|
||||
COLLECTION: '/collections',
|
||||
LINK: '/api/link',
|
||||
},
|
||||
NOT_FOUND: '/404',
|
||||
SERVER_ERROR: '/505',
|
||||
AUTHOR: 'https://www.sonny.dev/',
|
||||
REPO_GITHUB: 'https://github.com/Sonny93/my-links',
|
||||
EXTENSION: 'https://chromewebstore.google.com/detail/mylinks/agkmlplihacolkakgeccnbhphnepphma',
|
||||
EXTENSION:
|
||||
'https://chromewebstore.google.com/detail/mylinks/agkmlplihacolkakgeccnbhphnepphma',
|
||||
} as const;
|
||||
|
||||
export default PATHS;
|
||||
|
||||
@@ -4,8 +4,17 @@ import { collectionValidator } from '#validators/collection';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
|
||||
export default class CollectionsController {
|
||||
async index({ inertia }: HttpContext) {
|
||||
return inertia.render('app');
|
||||
async index({ auth, inertia }: HttpContext) {
|
||||
const collections = await Collection.findManyBy('author_id', auth.user!.id);
|
||||
|
||||
const collectionsWithLinks = await Promise.all(
|
||||
collections.map((collection) => {
|
||||
collection.load('links');
|
||||
return collection;
|
||||
})
|
||||
);
|
||||
|
||||
return inertia.render('dashboard', { collections: collectionsWithLinks });
|
||||
}
|
||||
|
||||
async showCreatePage({ inertia }: HttpContext) {
|
||||
@@ -25,6 +34,6 @@ export default class CollectionsController {
|
||||
response: HttpContext['response'],
|
||||
collectionId: Collection['id']
|
||||
) {
|
||||
return response.redirect(`${PATHS.APP}?categoryId=${collectionId}`);
|
||||
return response.redirect(`${PATHS.DASHBOARD}?categoryId=${collectionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,14 @@ export default class UsersController {
|
||||
return response.redirect(this.redirectTo);
|
||||
}
|
||||
|
||||
const { email, id: providerId, name, nickName, avatarUrl, token } = await google.user();
|
||||
const {
|
||||
email,
|
||||
id: providerId,
|
||||
name,
|
||||
nickName,
|
||||
avatarUrl,
|
||||
token,
|
||||
} = await google.user();
|
||||
const user = await User.updateOrCreate(
|
||||
{
|
||||
email,
|
||||
@@ -49,7 +56,7 @@ export default class UsersController {
|
||||
session.flash('flash', 'Successfully authenticated');
|
||||
logger.info(`[${user.email}] auth success`);
|
||||
|
||||
response.redirect(this.redirectTo);
|
||||
response.redirect(PATHS.DASHBOARD);
|
||||
}
|
||||
|
||||
async logout({ auth, response, session }: HttpContext) {
|
||||
|
||||
@@ -31,4 +31,4 @@ services:
|
||||
|
||||
volumes:
|
||||
postgres_volume:
|
||||
name: postgres_test_adonisv6
|
||||
name: postgres_test_dev_stack
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/// <reference path="../../adonisrc.ts" />
|
||||
|
||||
import { resolvePageComponent } from '@adonisjs/inertia/helpers';
|
||||
import { createInertiaApp } from '@inertiajs/react';
|
||||
import { hydrateRoot } from 'react-dom/client';
|
||||
import { theme } from '~/styles/theme';
|
||||
|
||||
import '../i18n/index';
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'MyLinks';
|
||||
|
||||
createInertiaApp({
|
||||
@@ -13,7 +13,10 @@ createInertiaApp({
|
||||
title: (title) => `${appName}${title && ` - ${title}`}`,
|
||||
|
||||
resolve: (name) => {
|
||||
return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'));
|
||||
return resolvePageComponent(
|
||||
`../pages/${name}.tsx`,
|
||||
import.meta.glob('../pages/**/*.tsx')
|
||||
);
|
||||
},
|
||||
|
||||
setup({ el, App, props }) {
|
||||
|
||||
40
inertia/components/footer/footer.tsx
Normal file
40
inertia/components/footer/footer.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import PATHS from '#constants/paths';
|
||||
import styled from '@emotion/styled';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ExternalLink from '~/components/common/external_link';
|
||||
import packageJson from '../../../package.json';
|
||||
|
||||
const FooterStyle = styled.footer(({ theme }) => ({
|
||||
fontSize: '0.9em',
|
||||
color: theme.colors.grey,
|
||||
textAlign: 'center',
|
||||
paddingTop: '0.75em',
|
||||
}));
|
||||
|
||||
export default function Footer() {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
return (
|
||||
<FooterStyle>
|
||||
<div className="row">
|
||||
<Link href={PATHS.PRIVACY}>{t('privacy')}</Link>
|
||||
{' • '}
|
||||
<Link href={PATHS.TERMS}>{t('terms')}</Link>
|
||||
{' • '}
|
||||
<ExternalLink href={PATHS.EXTENSION}>Extension</ExternalLink>
|
||||
</div>
|
||||
<div className="row">
|
||||
{t('footer.made_by')}{' '}
|
||||
<ExternalLink href={PATHS.AUTHOR}>Sonny</ExternalLink>
|
||||
{' • '}
|
||||
<span>
|
||||
Version:{' '}
|
||||
<ExternalLink href={PATHS.REPO_GITHUB}>
|
||||
{packageJson.version}
|
||||
</ExternalLink>
|
||||
</span>
|
||||
</div>
|
||||
</FooterStyle>
|
||||
);
|
||||
}
|
||||
18
inertia/components/layouts/dashboard_layout.tsx
Normal file
18
inertia/components/layouts/dashboard_layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import BaseLayout from './_base_layout';
|
||||
|
||||
const DashboardLayoutStyle = styled.div(({ theme }) => ({
|
||||
height: 'auto',
|
||||
width: theme.media.small_desktop,
|
||||
maxWidth: '100%',
|
||||
padding: '0.75em 1em',
|
||||
}));
|
||||
|
||||
const DashboardLayout = ({ children }: { children: ReactNode }) => (
|
||||
<BaseLayout>
|
||||
<DashboardLayoutStyle>{children}</DashboardLayoutStyle>
|
||||
</BaseLayout>
|
||||
);
|
||||
|
||||
export default DashboardLayout;
|
||||
@@ -11,6 +11,7 @@ const FormLayoutStyle = styled.div(({ theme }) => ({
|
||||
width: theme.media.mobile,
|
||||
maxWidth: '100%',
|
||||
marginTop: '10em',
|
||||
paddingInline: '1em',
|
||||
display: 'flex',
|
||||
gap: '0.75em',
|
||||
flexDirection: 'column',
|
||||
@@ -46,7 +47,7 @@ const FormLayout = ({
|
||||
</Form>
|
||||
{!disableHomeLink && (
|
||||
// <Link href={categoryId ? `/?categoryId=${categoryId}` : '/'}>{t('common:back-home')}</Link>
|
||||
<Link href={PATHS.APP}>← Revenir à l'accueil</Link>
|
||||
<Link href={PATHS.DASHBOARD}>← Revenir à l'accueil</Link>
|
||||
)}
|
||||
</FormLayoutStyle>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function Navbar() {
|
||||
{isAuthenticated && !!user ? (
|
||||
<>
|
||||
<li>
|
||||
<Link href={PATHS.APP}>Dashboard</Link>
|
||||
<Link href={PATHS.DASHBOARD}>Dashboard</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href={PATHS.AUTH.LOGOUT}>
|
||||
|
||||
64
inertia/i18n/index.ts
Normal file
64
inertia/i18n/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// TODO: Refacot this stuff
|
||||
// /!\ Code completion has to keep working
|
||||
|
||||
import frResourceAbout from './locales/fr/about.json';
|
||||
import frResourceAdmin from './locales/fr/admin.json';
|
||||
import frResourceCommon from './locales/fr/common.json';
|
||||
import frResourceHome from './locales/fr/home.json';
|
||||
import frResourceLogin from './locales/fr/login.json';
|
||||
import frResourcePrivacy from './locales/fr/privacy.json';
|
||||
import frResourceTerms from './locales/fr/terms.json';
|
||||
|
||||
import enResourceAbout from './locales/en/about.json';
|
||||
import enResourceAdmin from './locales/en/admin.json';
|
||||
import enResourceCommon from './locales/en/common.json';
|
||||
import enResourceHome from './locales/en/home.json';
|
||||
import enResourceLogin from './locales/en/login.json';
|
||||
import enResourcePrivacy from './locales/en/privacy.json';
|
||||
import enResourceTerms from './locales/en/terms.json';
|
||||
|
||||
type I18nFR =
|
||||
| RemoveSuffix<Leaves<typeof frResourceAbout>>
|
||||
| RemoveSuffix<Leaves<typeof frResourceAdmin>>
|
||||
| RemoveSuffix<Leaves<typeof frResourceCommon>>
|
||||
| RemoveSuffix<Leaves<typeof frResourceHome>>
|
||||
| RemoveSuffix<Leaves<typeof frResourceLogin>>
|
||||
| RemoveSuffix<Leaves<typeof frResourcePrivacy>>
|
||||
| RemoveSuffix<Leaves<typeof frResourceTerms>>;
|
||||
export type I18nKey = I18nFR;
|
||||
|
||||
export const resources = {
|
||||
en: {
|
||||
about: enResourceAbout,
|
||||
admin: enResourceAdmin,
|
||||
common: enResourceCommon,
|
||||
home: enResourceHome,
|
||||
login: enResourceLogin,
|
||||
privacy: enResourcePrivacy,
|
||||
terms: enResourceTerms,
|
||||
},
|
||||
fr: {
|
||||
about: frResourceAbout,
|
||||
admin: frResourceAdmin,
|
||||
common: frResourceCommon,
|
||||
home: frResourceHome,
|
||||
login: frResourceLogin,
|
||||
privacy: frResourcePrivacy,
|
||||
terms: frResourceTerms,
|
||||
},
|
||||
} as const;
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
returnNull: false,
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
defaultNS: 'common',
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
28
inertia/i18n/locales/en/about.json
Normal file
28
inertia/i18n/locales/en/about.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Welcome to MyLinks",
|
||||
"cta": "Get started"
|
||||
},
|
||||
"category": {
|
||||
"title": "Create categories",
|
||||
"text": "Organize your bookmarks by categories to keep your links tidy and find them easily."
|
||||
},
|
||||
"link": {
|
||||
"title": "Manage Links",
|
||||
"text": "Add, edit, and manage your bookmarks with a simple and intuitive interface."
|
||||
},
|
||||
"search": {
|
||||
"title": "Search",
|
||||
"text": "Quickly find the bookmark you're looking for with the powerful search feature."
|
||||
},
|
||||
"extension": {
|
||||
"title": "Browser extension",
|
||||
"text": "Enhance your experience with the official MyLinks browser extension."
|
||||
},
|
||||
"contribute": {
|
||||
"title": "Contribute to MyLinks",
|
||||
"text": "Suggest improvements you would like to see on MyLinks."
|
||||
},
|
||||
"look-title": "Take a look",
|
||||
"website-screenshot-alt": "A screenshot of MyLinks"
|
||||
}
|
||||
9
inertia/i18n/locales/en/admin.json
Normal file
9
inertia/i18n/locales/en/admin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"role": "Role",
|
||||
"created_at": "Created at",
|
||||
"updated_at": "Updated at",
|
||||
"admin": "Administrator",
|
||||
"user": "User",
|
||||
"users": "Users",
|
||||
"stats": "Statistics"
|
||||
}
|
||||
53
inertia/i18n/locales/en/common.json
Normal file
53
inertia/i18n/locales/en/common.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"slogan": "Manage your links in the best possible way",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"back-home": "← Back to home page",
|
||||
"logout": "Logout",
|
||||
"login": "Login",
|
||||
"link": {
|
||||
"links": "Links",
|
||||
"link": "Link",
|
||||
"name": "Link name",
|
||||
"description": "Link description",
|
||||
"create": "Create a link",
|
||||
"edit": "Edit a link",
|
||||
"remove": "Delete a link",
|
||||
"remove-confirm": "Confirm deletion?"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Categories",
|
||||
"category": "Category",
|
||||
"name": "Category name",
|
||||
"description": "Category description",
|
||||
"no-description": "No description",
|
||||
"visibility": "Public",
|
||||
"create": "Create a category",
|
||||
"edit": "Edit a category",
|
||||
"remove": "Delete a category",
|
||||
"remove-confirm": "Confirm deletion?",
|
||||
"remove-description": "You must delete all links in this category before you can delete this category."
|
||||
},
|
||||
"favorite": "Favorite",
|
||||
"no-item-found": "No item found",
|
||||
"search": "Search",
|
||||
"avatar": "{{name}}'s avatar",
|
||||
"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.",
|
||||
"retry": "Retry",
|
||||
"privacy": "Privacy",
|
||||
"terms": "Terms of use",
|
||||
"language": {
|
||||
"fr": "Français",
|
||||
"en": "English"
|
||||
},
|
||||
"lang": "Language",
|
||||
"settings": "Settings",
|
||||
"profile": "Profile",
|
||||
"select-your-lang": "Change the language",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"footer": {
|
||||
"made_by": "Made with ❤\uFE0F by"
|
||||
}
|
||||
}
|
||||
5
inertia/i18n/locales/en/home.json
Normal file
5
inertia/i18n/locales/en/home.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"select-category": "Please select a category",
|
||||
"or-create-one": "or create one",
|
||||
"no-link": "No link for <b>{{name}}</b>"
|
||||
}
|
||||
5
inertia/i18n/locales/en/login.json
Normal file
5
inertia/i18n/locales/en/login.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "Authentication",
|
||||
"informative-text": "Authentication required to use MyLinks",
|
||||
"continue-with": "Continue with {{provider}}"
|
||||
}
|
||||
45
inertia/i18n/locales/en/privacy.json
Normal file
45
inertia/i18n/locales/en/privacy.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Privacy Policy of MyLinks",
|
||||
"edited_at": "Last updated: {{date}}",
|
||||
"welcome": "Welcome to MyLinks, a free and open-source bookmark manager focused on privacy and self-hosting. This privacy policy aims to inform you about how we collect, use, and protect your data.",
|
||||
"collect": {
|
||||
"title": "1. Data Collection",
|
||||
"cookie": {
|
||||
"title": "1.1 Cookies",
|
||||
"description": "Cookies used on MyLinks are essential to ensure the proper functioning of the site. By continuing to use our service, you consent to the use of these cookies."
|
||||
},
|
||||
"user": {
|
||||
"title": "1.2 User Data",
|
||||
"description": "To create personalized categories and links and associate them with their author, we collect the following information:",
|
||||
"fields": ["Google ID", "Lastname", "Firstname", "Email", "Avatar"]
|
||||
}
|
||||
},
|
||||
"data_use": {
|
||||
"title": "2. Data Use",
|
||||
"description": "The collected data is neither resold nor used for purposes other than initially intended, namely the management of categories and links created by the user."
|
||||
},
|
||||
"data_storage": {
|
||||
"title": "3. Data Storage",
|
||||
"description": "Data is stored securely to protect your privacy.",
|
||||
"data_retention": {
|
||||
"title": "3.1 Data Retention Period",
|
||||
"description": "Functional data is retained until the user requests deletion. Once this request is made, the data will be permanently deleted."
|
||||
}
|
||||
},
|
||||
"user_rights": {
|
||||
"title": "4. User Rights",
|
||||
"description": "The user has the right to retrieve all their data at any time and/or request the complete deletion of their data."
|
||||
},
|
||||
"gdpr": {
|
||||
"title": "5. GDPR Compliance",
|
||||
"description": "MyLinks complies with the General Data Protection Regulation (GDPR) of the European Union."
|
||||
},
|
||||
"contact": {
|
||||
"title": "6. Contact",
|
||||
"description": "If you have any questions or concerns about our privacy policy, feel free to contact us at the following address:"
|
||||
},
|
||||
"footer": {
|
||||
"changes": "We reserve the right to update this privacy policy. We encourage you to regularly check this page to stay informed of any changes.",
|
||||
"thanks": "Thank you for using MyLinks!"
|
||||
}
|
||||
}
|
||||
62
inertia/i18n/locales/en/terms.json
Normal file
62
inertia/i18n/locales/en/terms.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"title": "Terms and Conditions of Use for MyLinks",
|
||||
"edited_at": "Last updated: {{date}}",
|
||||
"welcome": "Welcome to MyLinks, a free and open-source bookmark manager focused on privacy and self-hosting. By using this service, you agree to the terms and conditions of use outlined below. Please read them carefully.",
|
||||
"accept": {
|
||||
"title": "1. Acceptance of Terms",
|
||||
"description": "By accessing MyLinks and using our services, you agree to comply with these Terms and Conditions of Use."
|
||||
},
|
||||
"use": {
|
||||
"title": "2. Use of the Service",
|
||||
"account": {
|
||||
"title": "2.1 User Account",
|
||||
"description": "To access certain features of MyLinks, you will need to create a user account. You are responsible for the confidentiality of your account and credentials."
|
||||
},
|
||||
"allowed": {
|
||||
"title": "2.2 Authorized Use",
|
||||
"description": "You commit to using MyLinks in accordance with applicable laws and not violating the rights of third parties."
|
||||
},
|
||||
"user_content": {
|
||||
"title": "2.3 User Content",
|
||||
"description": "By posting content on MyLinks, you grant MyLinks a worldwide, non-exclusive, transferable, and free license to use, reproduce, distribute, and display this content."
|
||||
}
|
||||
},
|
||||
"personal_data": {
|
||||
"title": "3. Personal Data",
|
||||
"collect": {
|
||||
"title": "3.1 Collection and Use",
|
||||
"description": "The personal data collected is used in accordance with our <a>Privacy Policy</a>. By using MyLinks, you consent to this collection and use."
|
||||
},
|
||||
"suppress": {
|
||||
"title": "3.2 Account Deletion",
|
||||
"description": "You can request the deletion of your account at any time in accordance with our Privacy Policy."
|
||||
}
|
||||
},
|
||||
"responsibility_warranty": {
|
||||
"title": "4. Responsibilities and Warranties",
|
||||
"responsibility": {
|
||||
"title": "4.1 Responsibility",
|
||||
"description": "MyLinks cannot be held responsible for direct or indirect damages arising from the use of our services."
|
||||
},
|
||||
"warranty": {
|
||||
"title": "4.2 Warranties",
|
||||
"description": "MyLinks does not guarantee that the service will be free from errors or interruptions."
|
||||
}
|
||||
},
|
||||
"terms_changes": {
|
||||
"title": "5. Changes to the Terms",
|
||||
"description": "MyLinks reserves the right to modify these Terms and Conditions of Use at any time. Users will be notified of changes through a notification on the site."
|
||||
},
|
||||
"cancel": {
|
||||
"title": "6. Termination",
|
||||
"description": "MyLinks reserves the right to terminate or suspend your access to the service, with or without notice, in case of violation of these Terms and Conditions of Use."
|
||||
},
|
||||
"contact": {
|
||||
"title": "7. Contact",
|
||||
"description": "For any questions or concerns regarding these Terms and Conditions of Use, please contact us at the following address:"
|
||||
},
|
||||
"footer": {
|
||||
"changes": "We reserve the right to update these Terms and Conditions of Use. We encourage you to regularly check this page to stay informed of any changes.",
|
||||
"thanks": "Thank you for using MyLinks!"
|
||||
}
|
||||
}
|
||||
28
inertia/i18n/locales/fr/about.json
Normal file
28
inertia/i18n/locales/fr/about.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"hero": {
|
||||
"title": "Bienvenue sur MyLinks",
|
||||
"cta": "Lancez-vous !"
|
||||
},
|
||||
"category": {
|
||||
"title": "Créer des catégories",
|
||||
"text": "Organisez vos favoris dans des catégories pour garder vos liens en ordre et les retrouver facilement."
|
||||
},
|
||||
"link": {
|
||||
"title": "Gérer les liens",
|
||||
"text": "Ajoutez, modifiez et gérez vos favoris à l'aide d'une interface simple et intuitive."
|
||||
},
|
||||
"search": {
|
||||
"title": "Rechercher",
|
||||
"text": "Trouvez rapidement vos liens favoris en utilisant la fonction de recherche."
|
||||
},
|
||||
"extension": {
|
||||
"title": "Extension de navigateur",
|
||||
"text": "Améliorez votre expérience avec l'extension de navigateur officielle MyLinks."
|
||||
},
|
||||
"contribute": {
|
||||
"title": "Contribuer à MyLinks",
|
||||
"text": "Proposez des améliorations que vous souhaiteriez voir sur MyLinks."
|
||||
},
|
||||
"look-title": "Jetez un coup d'oeil",
|
||||
"website-screenshot-alt": "Une capture d'écran de MyLinks"
|
||||
}
|
||||
9
inertia/i18n/locales/fr/admin.json
Normal file
9
inertia/i18n/locales/fr/admin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"role": "Rôle",
|
||||
"created_at": "Création",
|
||||
"updated_at": "Mise à jour",
|
||||
"admin": "Administrateur",
|
||||
"user": "Utilisateur",
|
||||
"users": "Utilisateurs",
|
||||
"stats": "Statistiques"
|
||||
}
|
||||
53
inertia/i18n/locales/fr/common.json
Normal file
53
inertia/i18n/locales/fr/common.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"slogan": "Gérez vos liens de la meilleure des façons",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"back-home": "← Revenir à l'accueil",
|
||||
"logout": "Déconnexion",
|
||||
"login": "Connexion",
|
||||
"link": {
|
||||
"links": "Liens",
|
||||
"link": "Lien",
|
||||
"name": "Nom du lien",
|
||||
"description": "Description du lien",
|
||||
"create": "Créer un lien",
|
||||
"edit": "Modifier un lien",
|
||||
"remove": "Supprimer un lien",
|
||||
"remove-confirm": "Confirmer la suppression ?"
|
||||
},
|
||||
"category": {
|
||||
"categories": "Catégories",
|
||||
"category": "Catégorie",
|
||||
"name": "Nom de la catégorie",
|
||||
"description": "Description de la catégorie",
|
||||
"visibility": "Public",
|
||||
"no-description": "Aucune description",
|
||||
"create": "Créer une catégorie",
|
||||
"edit": "Modifier une catégorie",
|
||||
"remove": "Supprimer une catégorie",
|
||||
"remove-confirm": "Confirmer la suppression ?",
|
||||
"remove-description": "Vous devez supprimer tous les liens de cette catégorie avant de pouvoir supprimer cette catégorie"
|
||||
},
|
||||
"favorite": "Favoris",
|
||||
"no-item-found": "Aucun élément trouvé",
|
||||
"search": "Rechercher",
|
||||
"avatar": "Avatar de {{name}}",
|
||||
"generic-error": "Une erreur est survenue",
|
||||
"generic-error-description": "Une erreur est survenue, si cela se reproduit merci de <a href=\"https://github.com/Sonny93/my-links\" target=\"_blank\">créer une issue</a> avec le maximum de détails.",
|
||||
"retry": "Recommencer",
|
||||
"privacy": "Confidentialité",
|
||||
"terms": "CGU",
|
||||
"language": {
|
||||
"fr": "Français",
|
||||
"en": "English"
|
||||
},
|
||||
"lang": "Langage",
|
||||
"settings": "Paramètres",
|
||||
"profile": "Profil",
|
||||
"select-your-lang": "Modifier la langue",
|
||||
"name": "Nom",
|
||||
"email": "Email",
|
||||
"footer": {
|
||||
"made_by": "Fait avec ❤\uFE0F par"
|
||||
}
|
||||
}
|
||||
5
inertia/i18n/locales/fr/home.json
Normal file
5
inertia/i18n/locales/fr/home.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"select-category": "Veuillez sélectionner une categories",
|
||||
"or-create-one": "ou en créer une",
|
||||
"no-link": "Aucun lien pour <b>{{name}}</b>"
|
||||
}
|
||||
5
inertia/i18n/locales/fr/login.json
Normal file
5
inertia/i18n/locales/fr/login.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "Authentification",
|
||||
"informative-text": "Authentification requise pour utiliser MyLinks",
|
||||
"continue-with": "Continuer avec {{provider}}"
|
||||
}
|
||||
51
inertia/i18n/locales/fr/privacy.json
Normal file
51
inertia/i18n/locales/fr/privacy.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"title": "Politique de confidentialité de MyLinks",
|
||||
"edited_at": "Dernière mise à jour : {{date}}",
|
||||
"welcome": "Bienvenue sur MyLinks, un gestionnaire de favoris gratuit et open source axé sur la privacy et le self hosting. Cette politique de confidentialité vise à vous informer sur la manière dont nous collectons, utilisons et protégeons vos données.",
|
||||
"collect": {
|
||||
"title": "1. Collecte de données",
|
||||
"cookie": {
|
||||
"title": "1.1 Cookies",
|
||||
"description": "Les cookies utilisés sur MyLinks sont indispensables pour assurer le bon fonctionnement du site. En continuant à utiliser notre service, vous consentez à l'utilisation de ces cookies."
|
||||
},
|
||||
"user": {
|
||||
"title": "1.2 Données utilisateur",
|
||||
"description": "Pour créer des catégories et liens personnalisés et les associer à leur auteur, nous collectons les informations suivantes :",
|
||||
"fields": [
|
||||
"Identifiant Google",
|
||||
"Nom",
|
||||
"Prénom",
|
||||
"Adresse e-mail",
|
||||
"Avatar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"data_use": {
|
||||
"title": "2. Utilisation des données",
|
||||
"description": "Les données collectées ne sont ni revendues ni utilisées à d'autres fins que celles prévues initialement, à savoir la gestion des catégories et des liens créés par l'utilisateur."
|
||||
},
|
||||
"data_storage": {
|
||||
"title": "3. Stockage des données",
|
||||
"description": "Les données sont stockées de manière sécurisée afin de protéger votre confidentialité.",
|
||||
"data_retention": {
|
||||
"title": "3.1 Durée de conservation",
|
||||
"description": "Les données fonctionnelles sont conservées jusqu'à ce que l'utilisateur fasse une demande de suppression. Une fois cette demande effectuée, les données seront définitivement supprimées."
|
||||
}
|
||||
},
|
||||
"user_rights": {
|
||||
"title": "4. Droits de l'utilisateur",
|
||||
"description": "L'utilisateur a le droit de récupérer l'ensemble de ses données à tout moment et/ou de demander la suppression complète de ses données."
|
||||
},
|
||||
"gdpr": {
|
||||
"title": "5. Conformité au RGPD",
|
||||
"description": "MyLinks est conforme au Règlement Général sur la Protection des Données (RGPD) de l'Union européenne."
|
||||
},
|
||||
"contact": {
|
||||
"title": "6. Contact",
|
||||
"description": "Si vous avez des questions ou des préoccupations concernant notre politique de confidentialité, n'hésitez pas à nous contacter à l'adresse suivante :"
|
||||
},
|
||||
"footer": {
|
||||
"changes": "Nous nous réservons le droit de mettre à jour cette politique de confidentialité. Nous vous encourageons à consulter régulièrement cette page pour rester informé des changements éventuels.",
|
||||
"thanks": "Merci d'utiliser MyLinks !"
|
||||
}
|
||||
}
|
||||
62
inertia/i18n/locales/fr/terms.json
Normal file
62
inertia/i18n/locales/fr/terms.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"title": "Conditions Générales d'Utilisation de MyLinks",
|
||||
"edited_at": "Dernière mise à jour : {{date}}",
|
||||
"welcome": "Bienvenue sur MyLinks, un gestionnaire de favoris gratuit et open source axé sur la privacy et le self hosting. En utilisant ce service, vous acceptez les conditions générales d'utilisation énoncées ci-dessous. Veuillez les lire attentivement.",
|
||||
"accept": {
|
||||
"title": "1. Acceptation des Conditions",
|
||||
"description": "En accédant à MyLinks et en utilisant nos services, vous acceptez de vous conformer à ces Conditions Générales d'Utilisation."
|
||||
},
|
||||
"use": {
|
||||
"title": "2. Utilisation du Service",
|
||||
"account": {
|
||||
"title": "2.1 Compte Utilisateur",
|
||||
"description": "Pour accéder à certaines fonctionnalités de MyLinks, vous devrez créer un compte utilisateur. Vous êtes responsable de la confidentialité de votre compte et de vos informations d'identification."
|
||||
},
|
||||
"allowed": {
|
||||
"title": "2.2 Utilisation Autorisée",
|
||||
"description": "Vous vous engagez à utiliser MyLinks conformément aux lois en vigueur et à ne pas violer les droits de tiers."
|
||||
},
|
||||
"user_content": {
|
||||
"title": "2.3 Contenu Utilisateur",
|
||||
"description": "En publiant du contenu sur MyLinks, vous accordez à MyLinks une licence mondiale, non exclusive, transférable et gratuite pour utiliser, reproduire, distribuer et afficher ce contenu."
|
||||
}
|
||||
},
|
||||
"personal_data": {
|
||||
"title": "3. Données Personnelles",
|
||||
"collect": {
|
||||
"title": "3.1 Collecte et Utilisation",
|
||||
"description": "Les données personnelles collectées sont utilisées conformément à notre <a>Politique de Confidentialité</a>. En utilisant MyLinks, vous consentez à cette collecte et utilisation."
|
||||
},
|
||||
"suppress": {
|
||||
"title": "3.2 Suppression de Compte",
|
||||
"description": "Vous pouvez demander la suppression de votre compte à tout moment conformément à notre Politique de Confidentialité."
|
||||
}
|
||||
},
|
||||
"responsibility_warranty": {
|
||||
"title": "4. Responsabilités et Garanties",
|
||||
"responsibility": {
|
||||
"title": "4.1 Responsabilité",
|
||||
"description": "MyLinks ne peut être tenu responsable des dommages directs ou indirects découlant de l'utilisation de nos services."
|
||||
},
|
||||
"warranty": {
|
||||
"title": "4.2 Garanties",
|
||||
"description": "MyLinks ne garantit pas que le service sera exempt d'erreurs ou de interruptions."
|
||||
}
|
||||
},
|
||||
"terms_changes": {
|
||||
"title": "5. Modifications des Conditions",
|
||||
"description": "MyLinks se réserve le droit de modifier ces Conditions Générales\n d'Utilisation à tout moment. Les utilisateurs seront informés des\n changements par le biais d'une notification sur le site."
|
||||
},
|
||||
"cancel": {
|
||||
"title": "6. Résiliation",
|
||||
"description": "MyLinks se réserve le droit de résilier ou de suspendre votre accès au service, avec ou sans préavis, en cas de violation de ces Conditions Générales d'Utilisation."
|
||||
},
|
||||
"contact": {
|
||||
"title": "7. Contact",
|
||||
"description": "Pour toute question ou préoccupation concernant ces Conditions Générales d'Utilisation, veuillez nous contacter à l'adresse suivante :"
|
||||
},
|
||||
"footer": {
|
||||
"changes": "Nous nous réservons le droit de mettre à jour ces Conditions Générales d'Utilisation. Nous vous encourageons à consulter régulièrement cette page pour rester informé des changements éventuels.",
|
||||
"thanks": "Merci d'utiliser MyLinks !"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
|
||||
export default function AppPage() {
|
||||
return (
|
||||
<div>
|
||||
<Link href="/collections/create">Add collection</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
inertia/pages/dashboard.tsx
Normal file
22
inertia/pages/dashboard.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import type Collection from '#models/collection';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import Footer from '~/components/footer/footer';
|
||||
import DashboardLayout from '~/components/layouts/dashboard_layout';
|
||||
|
||||
// type DashboardPageProps = InferPageProps<CollectionsController, 'index'>
|
||||
type DashboardPageProps = {
|
||||
collections: Collection[];
|
||||
};
|
||||
|
||||
export default function DashboardPage({ collections }: DashboardPageProps) {
|
||||
console.log(collections);
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Link href="/collections/create">Add collection</Link>
|
||||
{collections.map((collection) => (
|
||||
<li key={collection.id}>{collection.name}</li>
|
||||
))}
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
@@ -2,13 +2,17 @@
|
||||
"extends": "@adonisjs/tsconfig/tsconfig.client.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"lib": ["DOM", "ESNext", "DOM.Iterable", "ES2020"],
|
||||
"target": "ESNext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"jsxImportSource": "@emotion/react",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
},
|
||||
|
||||
9
inertia/types/i18n.d.ts
vendored
Normal file
9
inertia/types/i18n.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'i18next';
|
||||
import { resources } from '../i18n';
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
returnNull: false;
|
||||
resources: (typeof resources)['fr'];
|
||||
}
|
||||
}
|
||||
24
inertia/types/utils.d.ts
vendored
Normal file
24
inertia/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
type Join<K, P> = K extends string
|
||||
? P extends string
|
||||
? `${K}${'' extends P ? '' : '.'}${P}`
|
||||
: never
|
||||
: never;
|
||||
|
||||
type Paths<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]-?: K extends string
|
||||
? `${K}` | Join<K, Paths<T[K]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
|
||||
type Leaves<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]-?: Join<K, Leaves<T[K]>>;
|
||||
}[keyof T]
|
||||
: '';
|
||||
|
||||
type RemoveSuffix<
|
||||
Key extends string,
|
||||
SuffixAfter extends string = '_',
|
||||
> = Key extends `${infer Prefix}${SuffixAfter}${string}` ? Prefix : Key;
|
||||
61
package-lock.json
generated
61
package-lock.json
generated
@@ -24,10 +24,12 @@
|
||||
"@inertiajs/react": "^1.0.16",
|
||||
"@vinejs/vine": "^2.0.0",
|
||||
"edge.js": "^6.0.2",
|
||||
"i18next": "^23.11.3",
|
||||
"luxon": "^3.4.4",
|
||||
"pg": "^8.11.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^14.1.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
@@ -6312,6 +6314,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
@@ -6368,6 +6378,28 @@
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "23.11.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.3.tgz",
|
||||
"integrity": "sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -8742,6 +8774,27 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz",
|
||||
"integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@@ -10599,6 +10652,14 @@
|
||||
"vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
|
||||
@@ -75,10 +75,12 @@
|
||||
"@inertiajs/react": "^1.0.16",
|
||||
"@vinejs/vine": "^2.0.0",
|
||||
"edge.js": "^6.0.2",
|
||||
"i18next": "^23.11.3",
|
||||
"luxon": "^3.4.4",
|
||||
"pg": "^8.11.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^14.1.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
|
||||
@@ -15,9 +15,9 @@ router.get('/auth/callback', [UsersController, 'callbackAuth']);
|
||||
router
|
||||
.group(() => {
|
||||
router.get(PATHS.AUTH.LOGOUT, [UsersController, 'logout']);
|
||||
router.get(PATHS.APP, [CollectionsController, 'index']);
|
||||
router.get(PATHS.DASHBOARD, [CollectionsController, 'index']);
|
||||
|
||||
router.get('/collections/create', [
|
||||
router.get(PATHS.COLLECTION.CREATE, [
|
||||
CollectionsController,
|
||||
'showCreatePage',
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user