From 6d8e6cf396b253b7a8a904a55a91c00d23ee9d4e Mon Sep 17 00:00:00 2001 From: Marc Gavanier Date: Wed, 13 Dec 2023 02:38:26 +0100 Subject: [PATCH] feat: add translation system --- src/app/(home)/_components/About.tsx | 30 ++ src/app/(home)/_components/Header.tsx | 40 ++ src/app/(home)/_components/index.ts | 2 + src/app/{ => (home)}/home.module.scss | 0 src/app/(home)/homeTranslation.ts | 8 + src/app/(home)/page.tsx | 442 ++++++++++++++++ src/app/_language/Language.tsx | 8 + src/app/_language/LanguageContext.ts | 7 + src/app/_language/LanguageSettings.ts | 8 + src/app/_language/index.ts | 3 + src/app/_translation/AvailableTranslations.ts | 12 + src/app/_translation/Translate.tsx | 12 + src/app/_translation/Translation.tsx | 45 ++ src/app/_translation/TranslationContext.ts | 5 + src/app/_translation/UseTranslation.ts | 4 + src/app/_translation/index.ts | 5 + src/app/_utils/asset.util.ts | 5 + src/app/_utils/index.ts | 3 + .../slug/slug.util.spec.ts} | 0 .../slug/slug.ts => _utils/slug/slug.util.ts} | 0 .../{utils => _utils}/url/url.util.spec.ts | 0 src/app/{utils => _utils}/url/url.util.ts | 0 src/app/common/index.ts | 1 - src/app/layout.tsx | 9 +- src/app/page.tsx | 472 ------------------ .../talks/[slug]/_components/TalkDetails.tsx | 46 ++ .../talks/[slug]/_components/TalkNotFound.tsx | 20 + src/app/talks/[slug]/_components/index.ts | 2 + src/app/talks/[slug]/page.tsx | 52 +- src/app/talks/[slug]/talk.ts | 2 +- src/app/talks/[slug]/talkTranslation.ts | 11 + src/app/utils/index.ts | 1 - src/dictionaries/en.json | 5 - src/dictionaries/fr.json | 5 - src/dictionaries/index.ts | 9 - src/settings/language.json | 4 + src/styles/components/_components.scss | 1 + src/translations/home/en.json | 8 + src/translations/home/fr.json | 8 + src/translations/talk/en.json | 11 + src/translations/talk/fr.json | 11 + 41 files changed, 773 insertions(+), 544 deletions(-) create mode 100644 src/app/(home)/_components/About.tsx create mode 100644 src/app/(home)/_components/Header.tsx create mode 100644 src/app/(home)/_components/index.ts rename src/app/{ => (home)}/home.module.scss (100%) create mode 100644 src/app/(home)/homeTranslation.ts create mode 100644 src/app/(home)/page.tsx create mode 100644 src/app/_language/Language.tsx create mode 100644 src/app/_language/LanguageContext.ts create mode 100644 src/app/_language/LanguageSettings.ts create mode 100644 src/app/_language/index.ts create mode 100644 src/app/_translation/AvailableTranslations.ts create mode 100644 src/app/_translation/Translate.tsx create mode 100644 src/app/_translation/Translation.tsx create mode 100644 src/app/_translation/TranslationContext.ts create mode 100644 src/app/_translation/UseTranslation.ts create mode 100644 src/app/_translation/index.ts create mode 100644 src/app/_utils/asset.util.ts create mode 100644 src/app/_utils/index.ts rename src/app/{common/slug/slug.spec.ts => _utils/slug/slug.util.spec.ts} (100%) rename src/app/{common/slug/slug.ts => _utils/slug/slug.util.ts} (100%) rename src/app/{utils => _utils}/url/url.util.spec.ts (100%) rename src/app/{utils => _utils}/url/url.util.ts (100%) delete mode 100644 src/app/common/index.ts delete mode 100644 src/app/page.tsx create mode 100644 src/app/talks/[slug]/_components/TalkDetails.tsx create mode 100644 src/app/talks/[slug]/_components/TalkNotFound.tsx create mode 100644 src/app/talks/[slug]/_components/index.ts create mode 100644 src/app/talks/[slug]/talkTranslation.ts delete mode 100644 src/app/utils/index.ts delete mode 100644 src/dictionaries/en.json delete mode 100644 src/dictionaries/fr.json delete mode 100644 src/dictionaries/index.ts create mode 100644 src/settings/language.json create mode 100644 src/translations/home/en.json create mode 100644 src/translations/home/fr.json create mode 100644 src/translations/talk/en.json create mode 100644 src/translations/talk/fr.json diff --git a/src/app/(home)/_components/About.tsx b/src/app/(home)/_components/About.tsx new file mode 100644 index 0000000..a6c4b29 --- /dev/null +++ b/src/app/(home)/_components/About.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { ReactElement } from 'react'; +import { Translate, useTranslation } from '../../_translation'; +import { HomeTranslation } from '../homeTranslation'; + +const About = (): ReactElement => { + const { about }: HomeTranslation = useTranslation(); + + return ( +
+
+
+
+

+ {about.subtitle} {about.title} +

+

{about.description}

+
+ +
+
+
+
+ ); +}; + +export default Translate(About, 'home'); diff --git a/src/app/(home)/_components/Header.tsx b/src/app/(home)/_components/Header.tsx new file mode 100644 index 0000000..4643e9d --- /dev/null +++ b/src/app/(home)/_components/Header.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { ReactElement, useContext } from 'react'; +import { NavDropdown } from 'react-bootstrap'; +import Container from 'react-bootstrap/Container'; +import Nav from 'react-bootstrap/Nav'; +import Navbar from 'react-bootstrap/Navbar'; +import { LanguageContext } from '../../_language'; + +export const Header = (): ReactElement => { + const [selectedLanguage, changeSelectedLanguage]: LanguageContext = useContext(LanguageContext); + // todo: translate + // const {header}: HomeTranslation = useTranslation() + + return ( + + + FreelanceConnect + + + + + + + ); +}; diff --git a/src/app/(home)/_components/index.ts b/src/app/(home)/_components/index.ts new file mode 100644 index 0000000..88cdec0 --- /dev/null +++ b/src/app/(home)/_components/index.ts @@ -0,0 +1,2 @@ +export * from './About'; +export * from './Header'; diff --git a/src/app/home.module.scss b/src/app/(home)/home.module.scss similarity index 100% rename from src/app/home.module.scss rename to src/app/(home)/home.module.scss diff --git a/src/app/(home)/homeTranslation.ts b/src/app/(home)/homeTranslation.ts new file mode 100644 index 0000000..9b9c778 --- /dev/null +++ b/src/app/(home)/homeTranslation.ts @@ -0,0 +1,8 @@ +export type HomeTranslation = { + about: { + title: string; + subtitle: string; + description: string; + learnMore: string; + }; +}; diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx new file mode 100644 index 0000000..945bd4e --- /dev/null +++ b/src/app/(home)/page.tsx @@ -0,0 +1,442 @@ +'use client'; + +import Image from 'next/image'; +import Link from 'next/link'; +import { ReactElement } from 'react'; +import { Badge } from 'react-bootstrap'; +import { asset } from '../_utils'; +import { Header } from './_components'; +import About from './_components/About'; +import styles from './home.module.scss'; + +const Home = (): ReactElement => { + return ( + <> +
+
+
+
+
+

+ Prochaine édition à venir en 2024 + La plus grande conférence dédiée aux freelances +

+

+ Esprits libres et créatifs qui prenez en main votre destin professionnel. Rejoignez la journée des freelances, + l’événement incontournable pour les freelances ambitieux et visionnaires de la région lyonnaise. +

{' '} + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+

+ Programme de la conférence + Retrouvez la liste des événements prévus +

+

+ Le programme se déroule sur une journée de 08h30 à 18h00, des conférences au format 40 minutes et 20 minutes + s’enchaîneront tout au long de la journée. +

+
+ + Voir le programme + +
+
+
+
+
+ 08:30 - 09:00 +
+
+
+ 1 +
+
+
+

Accueil

+ Récupérez votre badge pour la journée, puis prenez le temps du petit-déjeuner pour discuter un café à la + main. +
+
+
+
+ 09:40 - 12:00 +
+
+
+ 2 +
+
+
+

Première partie

+ Plénière d’ouverture, suivi de 9 de conférences réparties sur 3 pistes. +
+
+
+
+ 12:00 - 13:30 +
+
+
+ 3 +
+
+
+

Déjeuner

+ Profitez d’un repas prévu sur place. Tout est inclus dans le prix du billet. +
+
+
+
+ 13:30 - 17:40 +
+
+
+ 4 +
+
+
+

Seconde partie

+ 12 conférences sont prévues l’après-midi, elles sont également réparties sur 3 pistes. +
+
+
+
+ 17:50 - 18:30 +
+
+
+ 5 +
+
+
+

Fermeture

+ Plénière de fermeture suivi d’un apéro avant de se quitter. +
+
+
+
+
+
+
+
+
+
+

+ Les orateurs de la conférence + Les conférenciers +

+

+ Des experts renommés dans leurs domaines partageront leurs connaissances pour vous aider à vous épanouir en + tant que freelance +

+
+
+
+
+
+ +
+ +

Élodie Thibault

+

Responsable des Relations Humaines chez YouFair

+
+
+
+ +
+ +

Jean Couvreur

+

Contrôleur de gestion

+
+
+
+ +
+

Michel Mignard

+

Chargée de communication indépendant

+
+
+
+ +
+

Pierre Beaulne

+

Responsable juridique et Référent déontologue

+
+
+
+ +
+

Amélie Desjardins

+

Key Account Manager

+
+
+
+ +
+

Sandra Béliveau

+

Digital Ecosystem Builder

+
+
+
+ +
+

Corinne Feng

+

Product manager @Benka

+
+
+
+ +
+

Yannis Benett

+

Consultant en cybersécurité

+
+
+
+ + Voir tous les orateurs + +
+
+
+
+
+
+
+

+ Réservez votre place + Choisissez le billet qui vous convient parmi nos offres +

+

+ Nous vous proposons trois offres affin que vous puissiez choisir ce qui correspond le mieux à vos moyens. +

+
+
+
+
+
+
+
+ + Early bird + +
+ 29.99€ +
+
+
+

+ L’offre Early bird donne l’accès à un tarif préférentiel, le nombre de billets à ce tarif est + limité à 60 places, les premiers arrivés sont les premiers servis ! +

+
+ +
+
+
+
+
+
+
+
+ + Normal + +
+ 34.99€ +
+
+
+

+ L’offre Normal donne l’accès à la conférence, il n’y a pas de limite de places pour ce tarif. +

+
+ +
+
+
+
+
+
+
+
+ + Soutien + +
+ 49.99€ +
+
+
+

+ En choisissant l’offre de soutien, vous participez au côté de nos sponsors à rendre cet événement + possible en contribuant à assurer l’équilibre économique et nous vous{' '} + en remercions chaleureusement ! +

+
+ +
+
+
+
+
+
+
+
+
+
+

+ Sponsors de la conférence + Merci à nos sponsors pour leur soutien +

+

+ Sans le soutien de nos sponsors nous n’aurions pas pu vous proposer une conférence d’une telle qualité, nous + tenons ici à les remercier tout particulièrement pour leur aide. +

+
+
+
+
+ + ); +}; + +export default Home; diff --git a/src/app/_language/Language.tsx b/src/app/_language/Language.tsx new file mode 100644 index 0000000..7f66efc --- /dev/null +++ b/src/app/_language/Language.tsx @@ -0,0 +1,8 @@ +'use client'; + +import { ReactNode, useState } from 'react'; +import { LanguageContext } from './LanguageContext'; + +export const Language = ({ defaultLanguage, children }: { defaultLanguage: string; children: ReactNode }): ReactNode => ( + {children} +); diff --git a/src/app/_language/LanguageContext.ts b/src/app/_language/LanguageContext.ts new file mode 100644 index 0000000..7c13003 --- /dev/null +++ b/src/app/_language/LanguageContext.ts @@ -0,0 +1,7 @@ +'use client'; + +import { Context, createContext, Dispatch, SetStateAction } from 'react'; + +export type LanguageContext = [string, Dispatch>]; + +export const LanguageContext: Context = createContext([] as unknown as LanguageContext); diff --git a/src/app/_language/LanguageSettings.ts b/src/app/_language/LanguageSettings.ts new file mode 100644 index 0000000..f77bcbb --- /dev/null +++ b/src/app/_language/LanguageSettings.ts @@ -0,0 +1,8 @@ +import * as languageSettingsJSON from '../../settings/language.json'; + +export type LanguageSettings = { + availableLanguages: string[]; + defaultLanguage: string; +}; + +export const LANGUAGE_SETTINGS: LanguageSettings = languageSettingsJSON; diff --git a/src/app/_language/index.ts b/src/app/_language/index.ts new file mode 100644 index 0000000..533bcb6 --- /dev/null +++ b/src/app/_language/index.ts @@ -0,0 +1,3 @@ +export * from './Language'; +export * from './LanguageSettings'; +export * from './LanguageContext'; diff --git a/src/app/_translation/AvailableTranslations.ts b/src/app/_translation/AvailableTranslations.ts new file mode 100644 index 0000000..725936b --- /dev/null +++ b/src/app/_translation/AvailableTranslations.ts @@ -0,0 +1,12 @@ +import { LANGUAGE_SETTINGS } from '../_language'; + +type AvailableTranslations = Record<(typeof LANGUAGE_SETTINGS.availableLanguages)[number], () => Promise>; + +export const availableTranslations = (page: string) => + LANGUAGE_SETTINGS.availableLanguages.reduce( + (translations: AvailableTranslations, language: string): AvailableTranslations => ({ + ...translations, + [language]: () => import(`../../translations/${page}/${language}.json`).then((module: { default: T }) => module.default) + }), + {} + ); diff --git a/src/app/_translation/Translate.tsx b/src/app/_translation/Translate.tsx new file mode 100644 index 0000000..2939a89 --- /dev/null +++ b/src/app/_translation/Translate.tsx @@ -0,0 +1,12 @@ +import { ReactElement, ReactNode } from 'react'; +import { LANGUAGE_SETTINGS } from '../_language'; +import { availableTranslations } from './AvailableTranslations'; +import { Translation } from './Translation'; + +export const Translate = + (WrappedComponent: (props: T) => ReactNode, page: string) => + async (props: T): Promise => ( + + + + ); diff --git a/src/app/_translation/Translation.tsx b/src/app/_translation/Translation.tsx new file mode 100644 index 0000000..7bc7140 --- /dev/null +++ b/src/app/_translation/Translation.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { Dispatch, ReactNode, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; +import { LANGUAGE_SETTINGS, LanguageContext } from '../_language'; +import { availableTranslations } from './AvailableTranslations'; +import { TranslationContext } from './TranslationContext'; + +const updateTranslation = + (page: string, language: string) => + (onUpdate: (translation: T) => void): void => { + (async (): Promise => await availableTranslations(page)[language]!())().then(onUpdate); + }; + +export const Translation = ({ + translation, + page, + children +}: { + translation: T; + page: string; + children: ReactNode; +}): ReactNode => { + const [language]: LanguageContext = useContext(LanguageContext); + const [localTranslation, setTranslation]: [T, Dispatch>] = useState(translation); + const translationsMap: Map = useMemo( + () => new Map([[LANGUAGE_SETTINGS.defaultLanguage, translation]]), + [translation] + ); + + useEffect( + (): void => + translationsMap.has(language) + ? setTranslation(translationsMap.get(language) as T) + : updateTranslation( + page, + language + )((availableTranslation: T): void => { + translationsMap.set(language, availableTranslation); + setTranslation(availableTranslation); + }), + [translationsMap, language, page] + ); + + return {children}; +}; diff --git a/src/app/_translation/TranslationContext.ts b/src/app/_translation/TranslationContext.ts new file mode 100644 index 0000000..a627683 --- /dev/null +++ b/src/app/_translation/TranslationContext.ts @@ -0,0 +1,5 @@ +'use client'; + +import { Context, createContext } from 'react'; + +export const TranslationContext: Context = createContext({}); diff --git a/src/app/_translation/UseTranslation.ts b/src/app/_translation/UseTranslation.ts new file mode 100644 index 0000000..cc8cc07 --- /dev/null +++ b/src/app/_translation/UseTranslation.ts @@ -0,0 +1,4 @@ +import { Context, useContext } from 'react'; +import { TranslationContext } from './TranslationContext'; + +export const useTranslation = () => useContext(TranslationContext as Context); diff --git a/src/app/_translation/index.ts b/src/app/_translation/index.ts new file mode 100644 index 0000000..d933394 --- /dev/null +++ b/src/app/_translation/index.ts @@ -0,0 +1,5 @@ +export * from './AvailableTranslations'; +export * from './Translate'; +export * from './Translation'; +export * from './TranslationContext'; +export * from './UseTranslation'; diff --git a/src/app/_utils/asset.util.ts b/src/app/_utils/asset.util.ts new file mode 100644 index 0000000..5f3bead --- /dev/null +++ b/src/app/_utils/asset.util.ts @@ -0,0 +1,5 @@ +const ASSET_PREFIX: string = process.env['NEXT_PUBLIC_ASSET_PREFIX'] ?? ''; + +export const asset = (path: string): string => { + return `${ASSET_PREFIX}/${path}`; +}; diff --git a/src/app/_utils/index.ts b/src/app/_utils/index.ts new file mode 100644 index 0000000..8aed478 --- /dev/null +++ b/src/app/_utils/index.ts @@ -0,0 +1,3 @@ +export * from './slug/slug.util'; +export * from './url/url.util'; +export * from './asset.util'; diff --git a/src/app/common/slug/slug.spec.ts b/src/app/_utils/slug/slug.util.spec.ts similarity index 100% rename from src/app/common/slug/slug.spec.ts rename to src/app/_utils/slug/slug.util.spec.ts diff --git a/src/app/common/slug/slug.ts b/src/app/_utils/slug/slug.util.ts similarity index 100% rename from src/app/common/slug/slug.ts rename to src/app/_utils/slug/slug.util.ts diff --git a/src/app/utils/url/url.util.spec.ts b/src/app/_utils/url/url.util.spec.ts similarity index 100% rename from src/app/utils/url/url.util.spec.ts rename to src/app/_utils/url/url.util.spec.ts diff --git a/src/app/utils/url/url.util.ts b/src/app/_utils/url/url.util.ts similarity index 100% rename from src/app/utils/url/url.util.ts rename to src/app/_utils/url/url.util.ts diff --git a/src/app/common/index.ts b/src/app/common/index.ts deleted file mode 100644 index b1d3f07..0000000 --- a/src/app/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './slug/slug'; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3f090b9..cdc706b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import '../styles/styles.scss'; import type { Metadata } from 'next'; import { ReactElement, ReactNode } from 'react'; +import { Language, LANGUAGE_SETTINGS } from './_language'; export const metadata: Metadata = { title: `Your Conference 2023 | Share Knowledge & Innovation`, @@ -8,9 +9,11 @@ export const metadata: Metadata = { 'Join us at Your Conference 2023 to explore the latest tech advancements, connect with experts, and foster innovation. Get your tickets now!' }; -const RootLayout = ({ children }: { children: ReactNode }): ReactElement => ( - - {children} +const RootLayout = async ({ children }: { children: ReactNode }): Promise => ( + + + {children} + ); diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 509d606..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,472 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import Link from 'next/link'; -import { ReactElement } from 'react'; -import { Badge } from 'react-bootstrap'; -import Container from 'react-bootstrap/Container'; -import Nav from 'react-bootstrap/Nav'; -import Navbar from 'react-bootstrap/Navbar'; -import { asset } from './asset'; -import styles from './home.module.scss'; - -const Home = (): ReactElement => ( - <> -
-
- - - FreelanceConnect - - - - - - -
-
-

- Prochaine édition à venir en 2024 - La plus grande conférence dédiée aux freelances -

-

- Esprits libres et créatifs qui prenez en main votre destin professionnel. Rejoignez la journée des freelances, - l’événement incontournable pour les freelances ambitieux et visionnaires de la région lyonnaise. -

- -
-
-
- -
-
-
-
-
-
-
-
-

- À propos de la conférence - Bienvenue à la plus grande conférence dédiée aux freelances -

-

- Esprits libres et créatifs qui prenez en main votre destin professionnel. Rejoignez la journée des freelances, - l’événement incontournable pour les freelances ambitieux et visionnaires de la région lyonnaise. -

-
- -
-
-
-
-
-
-
-
-
-

- Programme de la conférence - Retrouvez la liste des événements prévus -

-

- Le programme se déroule sur une journée de 08h30 à 18h00, des conférences au format 40 minutes et 20 minutes - s’enchaîneront tout au long de la journée. -

-
- - Voir le programme - -
-
-
-
-
- 08:30 - 09:00 -
-
-
- 1 -
-
-
-

Accueil

- Récupérez votre badge pour la journée, puis prenez le temps du petit-déjeuner pour discuter un café à la main. -
-
-
-
- 09:40 - 12:00 -
-
-
- 2 -
-
-
-

Première partie

- Plénière d’ouverture, suivi de 9 de conférences réparties sur 3 pistes. -
-
-
-
- 12:00 - 13:30 -
-
-
- 3 -
-
-
-

Déjeuner

- Profitez d’un repas prévu sur place. Tout est inclus dans le prix du billet. -
-
-
-
- 13:30 - 17:40 -
-
-
- 4 -
-
-
-

Seconde partie

- 12 conférences sont prévues l’après-midi, elles sont également réparties sur 3 pistes. -
-
-
-
- 17:50 - 18:30 -
-
-
- 5 -
-
-
-

Fermeture

- Plénière de fermeture suivi d’un apéro avant de se quitter. -
-
-
-
-
-
-
-
-
-
-

- Les orateurs de la conférence - Les conférenciers -

-

- Des experts renommés dans leurs domaines partageront leurs connaissances pour vous aider à vous épanouir en tant - que freelance -

-
-
-
-
-
- -
- -

Élodie Thibault

-

Responsable des Relations Humaines chez YouFair

-
-
-
- -
- -

Jean Couvreur

-

Contrôleur de gestion

-
-
-
- -
-

Michel Mignard

-

Chargée de communication indépendant

-
-
-
- -
-

Pierre Beaulne

-

Responsable juridique et Référent déontologue

-
-
-
- -
-

Amélie Desjardins

-

Key Account Manager

-
-
-
- -
-

Sandra Béliveau

-

Digital Ecosystem Builder

-
-
-
- -
-

Corinne Feng

-

Product manager @Benka

-
-
-
- -
-

Yannis Benett

-

Consultant en cybersécurité

-
-
-
- - Voir tous les orateurs - -
-
-
-
-
-
-
-

- Réservez votre place - Choisissez le billet qui vous convient parmi nos offres -

-

- Nous vous proposons trois offres affin que vous puissiez choisir ce qui correspond le mieux à vos moyens. -

-
-
-
-
-
-
-
- - Early bird - -
- 29.99€ -
-
-
-

- L’offre Early bird donne l’accès à un tarif préférentiel, le nombre de billets à ce tarif est limité - à 60 places, les premiers arrivés sont les premiers servis ! -

-
- -
-
-
-
-
-
-
-
- - Normal - -
- 34.99€ -
-
-
-

- L’offre Normal donne l’accès à la conférence, il n’y a pas de limite de places pour ce tarif. -

-
- -
-
-
-
-
-
-
-
- - Soutien - -
- 49.99€ -
-
-
-

- En choisissant l’offre de soutien, vous participez au côté de nos sponsors à rendre cet événement - possible en contribuant à assurer l’équilibre économique et nous vous en remercions chaleureusement ! -

-
- -
-
-
-
-
-
-
-
-
-
-

- Sponsors de la conférence - Merci à nos sponsors pour leur soutien -

-

- Sans le soutien de nos sponsors nous n’aurions pas pu vous proposer une conférence d’une telle qualité, nous - tenons ici à les remercier tout particulièrement pour leur aide. -

-
-
-
-
- -); - -export default Home; diff --git a/src/app/talks/[slug]/_components/TalkDetails.tsx b/src/app/talks/[slug]/_components/TalkDetails.tsx new file mode 100644 index 0000000..e7dc9bd --- /dev/null +++ b/src/app/talks/[slug]/_components/TalkDetails.tsx @@ -0,0 +1,46 @@ +'use client'; + +import Link from 'next/link'; +import { ReactElement } from 'react'; +import { Translate, useTranslation } from '../../../_translation'; +import { Talk } from '../talk'; +import { TalkTranslation } from '../talkTranslation'; + +export const TalkDetails = Translate(({ talk }: { talk: Talk }): ReactElement => { + const { talkDetails: i18n }: TalkTranslation = useTranslation(); + + return ( + <> +

{talk.title}

+

({talk.language})

+ + {i18n.by} {talk.speaker} - {talk.date.toLocaleString()} ({talk.duration} min) - {talk.room} + +
+

+ {talk.track} +

+

{talk.description}

+ {talk.tags?.map((tag: string) => #{tag} )} + {(talk.slidesLink || talk.videoLink) && ( + + )} + Back to talks + + ); +}, 'talk'); diff --git a/src/app/talks/[slug]/_components/TalkNotFound.tsx b/src/app/talks/[slug]/_components/TalkNotFound.tsx new file mode 100644 index 0000000..6b4988e --- /dev/null +++ b/src/app/talks/[slug]/_components/TalkNotFound.tsx @@ -0,0 +1,20 @@ +'use client'; + +import Link from 'next/link'; +import { ReactElement } from 'react'; +import { Translate, useTranslation } from '../../../_translation'; +import { TalkTranslation } from '../talkTranslation'; + +export const TalkNotFound = Translate((): ReactElement => { + const { notFound: i18n }: TalkTranslation = useTranslation(); + + return ( + <> +

{i18n.title}

+

+ {i18n.message} {i18n.link} +

+ {i18n.back} + + ); +}, 'talk'); diff --git a/src/app/talks/[slug]/_components/index.ts b/src/app/talks/[slug]/_components/index.ts new file mode 100644 index 0000000..c2074bd --- /dev/null +++ b/src/app/talks/[slug]/_components/index.ts @@ -0,0 +1,2 @@ +export * from './TalkDetails'; +export * from './TalkNotFound'; diff --git a/src/app/talks/[slug]/page.tsx b/src/app/talks/[slug]/page.tsx index 35a7861..34e1500 100644 --- a/src/app/talks/[slug]/page.tsx +++ b/src/app/talks/[slug]/page.tsx @@ -1,57 +1,15 @@ import talksJson from '@/data/talks.json'; -import { Dictionary, dictionaryFor } from '@/dictionaries'; -import Link from 'next/link'; import { ReactElement } from 'react'; -import { bySlug } from '../../common'; +import { bySlug } from '../../_utils'; +import { TalkDetails, TalkNotFound } from './_components'; import { Talk, talksFromJSON } from './talk'; export const generateStaticParams = (): Talk[] => talksFromJSON(talksJson); -const TalkPage = async ({ params }: { params: Talk }): Promise => { - const dict: Dictionary = await dictionaryFor('en'); +const TalkPage = ({ params }: { params: Talk }): ReactElement => { const talk: Talk | undefined = talksFromJSON(talksJson).find(bySlug(params.slug)); - return talk ? ( - <> -

{talk.title}

-

({talk.language})

- - {dict.talks.by} {talk.speaker} - {talk.date.toLocaleString()} ({talk.duration} min) - {talk.room} - -
-

- {talk.track} -

-

{talk.description}

- {talk.tags?.map((tag: string) => #{tag} )} - {(talk.slidesLink || talk.videoLink) && ( - - )} - Back to talks - - ) : ( - <> -

This talk does not exist

-

- There is no talk for this url, select another talk form the list of talks -

- Back to talks - - ); + + return talk ? : ; }; export default TalkPage; diff --git a/src/app/talks/[slug]/talk.ts b/src/app/talks/[slug]/talk.ts index a0c1fb6..fab5484 100644 --- a/src/app/talks/[slug]/talk.ts +++ b/src/app/talks/[slug]/talk.ts @@ -1,4 +1,4 @@ -import { slugify } from '../../common'; +import { slugify } from '../../_utils'; type TalkBase = { title: string; diff --git a/src/app/talks/[slug]/talkTranslation.ts b/src/app/talks/[slug]/talkTranslation.ts new file mode 100644 index 0000000..452ca1f --- /dev/null +++ b/src/app/talks/[slug]/talkTranslation.ts @@ -0,0 +1,11 @@ +export type TalkTranslation = { + talkDetails: { + by: string; + }; + notFound: { + title: string; + message: string; + link: string; + back: string; + }; +}; diff --git a/src/app/utils/index.ts b/src/app/utils/index.ts deleted file mode 100644 index 5615cb5..0000000 --- a/src/app/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './url/url.util'; diff --git a/src/dictionaries/en.json b/src/dictionaries/en.json deleted file mode 100644 index 23d6d37..0000000 --- a/src/dictionaries/en.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "talks": { - "by": "By" - } -} diff --git a/src/dictionaries/fr.json b/src/dictionaries/fr.json deleted file mode 100644 index 5bc2af9..0000000 --- a/src/dictionaries/fr.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "talks": { - "by": "Par" - } -} diff --git a/src/dictionaries/index.ts b/src/dictionaries/index.ts deleted file mode 100644 index 55cf7b9..0000000 --- a/src/dictionaries/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -type Lang = 'en' | 'fr'; -export type Dictionary = { talks: { by: string } }; - -const index: Record Promise> = { - en: () => import('./en.json').then((module: { default: Dictionary }) => module.default), - fr: () => import('./fr.json').then((module: { default: Dictionary }) => module.default) -}; - -export const dictionaryFor = async (locale: Lang): Promise => index[locale](); diff --git a/src/settings/language.json b/src/settings/language.json new file mode 100644 index 0000000..d3838ea --- /dev/null +++ b/src/settings/language.json @@ -0,0 +1,4 @@ +{ + "availableLanguages": ["en", "fr"], + "defaultLanguage": "fr" +} diff --git a/src/styles/components/_components.scss b/src/styles/components/_components.scss index 4db3160..e12a2bd 100644 --- a/src/styles/components/_components.scss +++ b/src/styles/components/_components.scss @@ -5,6 +5,7 @@ @import 'bootstrap/scss/grid'; @import 'bootstrap/scss/buttons'; @import 'bootstrap/scss/transitions'; +@import 'bootstrap/scss/dropdown'; @import 'bootstrap/scss/button-group'; @import 'bootstrap/scss/nav'; @import 'bootstrap/scss/navbar'; diff --git a/src/translations/home/en.json b/src/translations/home/en.json new file mode 100644 index 0000000..77742cb --- /dev/null +++ b/src/translations/home/en.json @@ -0,0 +1,8 @@ +{ + "about": { + "title": "Welcome to the largest conference dedicated to freelancers", + "subtitle": "About the conference", + "description": "Free and creative minds taking control of your professional destiny. Join Freelancer Day, the must-attend event for ambitious and visionary freelancers in the Lyon region.", + "learnMore": "Learn more" + } +} diff --git a/src/translations/home/fr.json b/src/translations/home/fr.json new file mode 100644 index 0000000..e3363b4 --- /dev/null +++ b/src/translations/home/fr.json @@ -0,0 +1,8 @@ +{ + "about": { + "title": "Bienvenue à la plus grande conférence dédiée aux freelances", + "subtitle": "À propos de la conférence", + "description": "Esprits hackers et créatifs qui prenez en main votre destin professionnel. Rejoignez la journée des freelances, l’événement incontournable pour les freelances ambitieux et visionnaires de la région lyonnaise.", + "learnMore": "En savoir plus" + } +} diff --git a/src/translations/talk/en.json b/src/translations/talk/en.json new file mode 100644 index 0000000..37da9b4 --- /dev/null +++ b/src/translations/talk/en.json @@ -0,0 +1,11 @@ +{ + "talkDetails": { + "by": "By" + }, + "notFound": { + "title": "This talk does not exist", + "message": "There is no talk for this url, select another talk form the", + "link": "list of talks", + "back": "Back to talks" + } +} diff --git a/src/translations/talk/fr.json b/src/translations/talk/fr.json new file mode 100644 index 0000000..68459cc --- /dev/null +++ b/src/translations/talk/fr.json @@ -0,0 +1,11 @@ +{ + "talkDetails": { + "by": "Par" + }, + "notFound": { + "title": "Cette conférence n'existe pas", + "message": "Il n'y a pas de conférence associé à cet url, sélectionnez en une autre depuis", + "link": "la liste des conférences", + "back": "Retour à la liste des conférences" + } +}