aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Layouts/Layout.tsx3
-rw-r--r--src/components/MetaItems/Author/Author.tsx20
-rw-r--r--src/components/MetaItems/CommentsCount/CommentsCount.tsx41
-rw-r--r--src/components/MetaItems/Dates/Dates.tsx56
-rw-r--r--src/components/MetaItems/MetaItem/MetaItem.module.scss18
-rw-r--r--src/components/MetaItems/MetaItem/MetaItem.tsx36
-rw-r--r--src/components/MetaItems/PostsCount/PostsCount.tsx27
-rw-r--r--src/components/MetaItems/ReadingTime/ReadingTime.tsx55
-rw-r--r--src/components/MetaItems/Thematics/Thematics.tsx42
-rw-r--r--src/components/MetaItems/Topics/Topics.tsx36
-rw-r--r--src/components/MetaItems/Website/Website.tsx20
-rw-r--r--src/components/MetaItems/index.tsx21
-rw-r--r--src/components/PostHeader/PostHeader.tsx16
-rw-r--r--src/components/PostMeta/PostMeta.module.scss21
-rw-r--r--src/components/PostMeta/PostMeta.tsx236
-rw-r--r--src/i18n/en.json102
-rw-r--r--src/i18n/fr.json102
-rw-r--r--src/pages/article/[slug].tsx4
-rw-r--r--src/ts/types/app.ts2
19 files changed, 515 insertions, 343 deletions
diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx
index 845d6fa..23c1d0e 100644
--- a/src/components/Layouts/Layout.tsx
+++ b/src/components/Layouts/Layout.tsx
@@ -19,9 +19,8 @@ const Layout = ({
isHome?: boolean;
}) => {
const intl = useIntl();
- const { locale } = useRouter();
+ const { asPath, locale } = useRouter();
const ref = useRef<HTMLSpanElement>(null);
- const { asPath } = useRouter();
useEffect(() => {
ref.current?.focus();
diff --git a/src/components/MetaItems/Author/Author.tsx b/src/components/MetaItems/Author/Author.tsx
new file mode 100644
index 0000000..c3d78c2
--- /dev/null
+++ b/src/components/MetaItems/Author/Author.tsx
@@ -0,0 +1,20 @@
+import { MetaKind } from '@ts/types/app';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const Author = ({ name, kind }: { name: string; kind: MetaKind }) => {
+ const intl = useIntl();
+
+ return (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Written by:',
+ description: 'Author: article author meta label',
+ })}
+ value={name}
+ kind={kind}
+ />
+ );
+};
+
+export default Author;
diff --git a/src/components/MetaItems/CommentsCount/CommentsCount.tsx b/src/components/MetaItems/CommentsCount/CommentsCount.tsx
new file mode 100644
index 0000000..bd1990d
--- /dev/null
+++ b/src/components/MetaItems/CommentsCount/CommentsCount.tsx
@@ -0,0 +1,41 @@
+import { MetaKind } from '@ts/types/app';
+import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const CommentsCount = ({ total, kind }: { total: number; kind: MetaKind }) => {
+ const intl = useIntl();
+ const { asPath } = useRouter();
+
+ const isArticle = () => asPath.includes('/article/');
+
+ const getCommentsCount = () => {
+ return intl.formatMessage(
+ {
+ defaultMessage:
+ '{total, plural, =0 {No comments} one {# comment} other {# comments}}',
+ description: 'CommentsCount: comment count value',
+ },
+ { total }
+ );
+ };
+
+ return (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Comments:',
+ description: 'CommentsCount: comment count meta label',
+ })}
+ value={
+ isArticle() ? (
+ <a href="#comments">{getCommentsCount()}</a>
+ ) : (
+ getCommentsCount()
+ )
+ }
+ kind={kind}
+ />
+ );
+};
+
+export default CommentsCount;
diff --git a/src/components/MetaItems/Dates/Dates.tsx b/src/components/MetaItems/Dates/Dates.tsx
new file mode 100644
index 0000000..04dff3a
--- /dev/null
+++ b/src/components/MetaItems/Dates/Dates.tsx
@@ -0,0 +1,56 @@
+import { MetaKind } from '@ts/types/app';
+import { settings } from '@utils/config';
+import { getFormattedDate } from '@utils/helpers/format';
+import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const Dates = ({
+ publication,
+ update,
+ kind,
+}: {
+ publication: string;
+ update: string;
+ kind: MetaKind;
+}) => {
+ const intl = useIntl();
+ const { locale } = useRouter();
+ const validLocale = locale ? locale : settings.locales.defaultLocale;
+
+ const publicationDate = getFormattedDate(publication, validLocale);
+ const updateDate = getFormattedDate(update, validLocale);
+
+ return (
+ <>
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Published on:',
+ description: 'Dates: publication date meta label',
+ })}
+ values={[
+ <time key={publication} dateTime={publication}>
+ {publicationDate}
+ </time>,
+ ]}
+ kind={kind}
+ />
+ {publicationDate !== updateDate && (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Updated on:',
+ description: 'Dates: update date meta label',
+ })}
+ values={[
+ <time key={update} dateTime={update}>
+ {updateDate}
+ </time>,
+ ]}
+ kind={kind}
+ />
+ )}
+ </>
+ );
+};
+
+export default Dates;
diff --git a/src/components/MetaItems/MetaItem/MetaItem.module.scss b/src/components/MetaItems/MetaItem/MetaItem.module.scss
new file mode 100644
index 0000000..0b159ca
--- /dev/null
+++ b/src/components/MetaItems/MetaItem/MetaItem.module.scss
@@ -0,0 +1,18 @@
+.wrapper--article {
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.title--article {
+ margin-right: var(--spacing-2xs);
+ color: var(--color-fg-light);
+}
+
+.body--article {
+ &:not(:first-of-type) {
+ &::before {
+ content: "/";
+ margin: 0 var(--spacing-2xs);
+ }
+ }
+}
diff --git a/src/components/MetaItems/MetaItem/MetaItem.tsx b/src/components/MetaItems/MetaItem/MetaItem.tsx
new file mode 100644
index 0000000..5c51283
--- /dev/null
+++ b/src/components/MetaItems/MetaItem/MetaItem.tsx
@@ -0,0 +1,36 @@
+import { MetaKind } from '@ts/types/app';
+import { ReactElement } from 'react';
+import styles from './MetaItem.module.scss';
+
+const MetaItem = ({
+ title,
+ value,
+ values,
+ info,
+ kind = 'list',
+}: {
+ title: string;
+ value?: ReactElement | string;
+ values?: ReactElement[] | string[];
+ info?: string;
+ kind: MetaKind;
+}) => {
+ return (
+ <div className={styles[`wrapper--${kind}`]}>
+ <dt className={styles[`title--${kind}`]}>{title}</dt>
+ {value && (
+ <dd className={styles[`body--${kind}`]} title={info}>
+ {value}
+ </dd>
+ )}
+ {values &&
+ values.map((currentValue, index) => (
+ <dd key={index} className={styles[`body--${kind}`]} title={info}>
+ {currentValue}
+ </dd>
+ ))}
+ </div>
+ );
+};
+
+export default MetaItem;
diff --git a/src/components/MetaItems/PostsCount/PostsCount.tsx b/src/components/MetaItems/PostsCount/PostsCount.tsx
new file mode 100644
index 0000000..9fb1784
--- /dev/null
+++ b/src/components/MetaItems/PostsCount/PostsCount.tsx
@@ -0,0 +1,27 @@
+import { MetaKind } from '@ts/types/app';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const PostsCount = ({ total, kind }: { total: number; kind: MetaKind }) => {
+ const intl = useIntl();
+
+ return (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Total:',
+ description: 'PostCount: total found articles meta label',
+ })}
+ value={intl.formatMessage(
+ {
+ defaultMessage:
+ '{total, plural, =0 {No articles} one {# article} other {# articles}}',
+ description: 'PostCount: total found articles',
+ },
+ { total }
+ )}
+ kind={kind}
+ />
+ );
+};
+
+export default PostsCount;
diff --git a/src/components/MetaItems/ReadingTime/ReadingTime.tsx b/src/components/MetaItems/ReadingTime/ReadingTime.tsx
new file mode 100644
index 0000000..94215b3
--- /dev/null
+++ b/src/components/MetaItems/ReadingTime/ReadingTime.tsx
@@ -0,0 +1,55 @@
+import { MetaKind } from '@ts/types/app';
+import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const ReadingTime = ({
+ time,
+ words,
+ kind,
+}: {
+ time: number;
+ words: number;
+ kind: MetaKind;
+}) => {
+ const intl = useIntl();
+ const { locale } = useRouter();
+
+ const getEstimation = () => {
+ if (time < 0) {
+ return intl.formatMessage({
+ defaultMessage: 'less than 1 minute',
+ description: 'ReadingTime: Reading time value',
+ });
+ }
+
+ return intl.formatMessage(
+ {
+ defaultMessage:
+ '{time, plural, =0 {# minutes} one {# minute} other {# minutes}}',
+ description: 'ReadingTime: reading time value',
+ },
+ { time }
+ );
+ };
+
+ return (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Reading time:',
+ description: 'ReadingTime: reading time meta label',
+ })}
+ value={getEstimation()}
+ info={intl.formatMessage(
+ {
+ defaultMessage: `Approximately {number} words`,
+ description: 'ReadingTime: number of words',
+ },
+ { number: words.toLocaleString(locale) }
+ )}
+ kind={kind}
+ />
+ );
+};
+
+export default ReadingTime;
diff --git a/src/components/MetaItems/Thematics/Thematics.tsx b/src/components/MetaItems/Thematics/Thematics.tsx
new file mode 100644
index 0000000..a127715
--- /dev/null
+++ b/src/components/MetaItems/Thematics/Thematics.tsx
@@ -0,0 +1,42 @@
+import { MetaKind } from '@ts/types/app';
+import { ThematicPreview } from '@ts/types/taxonomies';
+import Link from 'next/link';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const Thematics = ({
+ list,
+ kind,
+}: {
+ list: ThematicPreview[];
+ kind: MetaKind;
+}) => {
+ const intl = useIntl();
+
+ const getThematics = () => {
+ return list.map((thematic) => {
+ return (
+ <Link key={thematic.databaseId} href={`/thematique/${thematic.slug}`}>
+ <a>{thematic.title}</a>
+ </Link>
+ );
+ });
+ };
+
+ return (
+ <MetaItem
+ title={intl.formatMessage(
+ {
+ defaultMessage:
+ '{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}',
+ description: 'Thematics: thematics list meta label',
+ },
+ { thematicsCount: list.length }
+ )}
+ values={getThematics()}
+ kind={kind}
+ />
+ );
+};
+
+export default Thematics;
diff --git a/src/components/MetaItems/Topics/Topics.tsx b/src/components/MetaItems/Topics/Topics.tsx
new file mode 100644
index 0000000..4f2dc1f
--- /dev/null
+++ b/src/components/MetaItems/Topics/Topics.tsx
@@ -0,0 +1,36 @@
+import { MetaKind } from '@ts/types/app';
+import { TopicPreview } from '@ts/types/taxonomies';
+import Link from 'next/link';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const Topics = ({ list, kind }: { list: TopicPreview[]; kind: MetaKind }) => {
+ const intl = useIntl();
+
+ const getTopics = () => {
+ return list.map((topic) => {
+ return (
+ <Link key={topic.databaseId} href={`/sujet/${topic.slug}`}>
+ <a>{topic.title}</a>
+ </Link>
+ );
+ });
+ };
+
+ return (
+ <MetaItem
+ title={intl.formatMessage(
+ {
+ defaultMessage:
+ '{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}',
+ description: 'Topics: topics list meta label',
+ },
+ { topicsCount: list.length }
+ )}
+ values={getTopics()}
+ kind={kind}
+ />
+ );
+};
+
+export default Topics;
diff --git a/src/components/MetaItems/Website/Website.tsx b/src/components/MetaItems/Website/Website.tsx
new file mode 100644
index 0000000..bcf3fc8
--- /dev/null
+++ b/src/components/MetaItems/Website/Website.tsx
@@ -0,0 +1,20 @@
+import { MetaKind } from '@ts/types/app';
+import { useIntl } from 'react-intl';
+import { MetaItem } from '..';
+
+const Website = ({ url, kind }: { url: string; kind: MetaKind }) => {
+ const intl = useIntl();
+
+ return (
+ <MetaItem
+ title={intl.formatMessage({
+ defaultMessage: 'Website:',
+ description: 'Website: website meta label',
+ })}
+ value={<a href={url}>{url}</a>}
+ kind={kind}
+ />
+ );
+};
+
+export default Website;
diff --git a/src/components/MetaItems/index.tsx b/src/components/MetaItems/index.tsx
new file mode 100644
index 0000000..e90d5a6
--- /dev/null
+++ b/src/components/MetaItems/index.tsx
@@ -0,0 +1,21 @@
+import Author from './Author/Author';
+import CommentsCount from './CommentsCount/CommentsCount';
+import Dates from './Dates/Dates';
+import MetaItem from './MetaItem/MetaItem';
+import PostsCount from './PostsCount/PostsCount';
+import ReadingTime from './ReadingTime/ReadingTime';
+import Thematics from './Thematics/Thematics';
+import Topics from './Topics/Topics';
+import Website from './Website/Website';
+
+export {
+ Author,
+ CommentsCount,
+ Dates,
+ MetaItem,
+ PostsCount,
+ ReadingTime,
+ Thematics,
+ Topics,
+ Website,
+};
diff --git a/src/components/PostHeader/PostHeader.tsx b/src/components/PostHeader/PostHeader.tsx
index f070583..c0a6b68 100644
--- a/src/components/PostHeader/PostHeader.tsx
+++ b/src/components/PostHeader/PostHeader.tsx
@@ -16,19 +16,6 @@ const PostHeader = ({
meta?: ArticleMeta;
title: string;
}) => {
- const hasMeta = () => {
- return (
- meta?.author ||
- meta?.commentCount ||
- meta?.dates ||
- meta?.readingTime ||
- meta?.results ||
- meta?.thematics ||
- meta?.website ||
- meta?.wordsCount
- );
- };
-
const getIntro = () => {
if (React.isValidElement(intro)) {
const Intro = () => intro;
@@ -38,6 +25,7 @@ const PostHeader = ({
</div>
);
}
+
return (
intro && (
<div
@@ -59,7 +47,7 @@ const PostHeader = ({
)}
{title}
</h1>
- {meta && hasMeta() && <PostMeta mode="single" meta={meta} />}
+ {meta && <PostMeta kind="article" meta={meta} />}
{getIntro()}
</div>
</header>
diff --git a/src/components/PostMeta/PostMeta.module.scss b/src/components/PostMeta/PostMeta.module.scss
index 6f8e1c2..d438635 100644
--- a/src/components/PostMeta/PostMeta.module.scss
+++ b/src/components/PostMeta/PostMeta.module.scss
@@ -18,7 +18,7 @@
}
}
- &--single {
+ &--article {
flex-flow: column wrap;
margin: var(--spacing-sm) 0 0;
@@ -27,24 +27,5 @@
font-size: var(--font-size-sm);
}
}
-
- .item {
- display: flex;
- flex-flow: row wrap;
- }
-
- .term {
- margin-right: var(--spacing-2xs);
- color: var(--color-fg-light);
- }
-
- .description {
- &:not(:first-of-type) {
- &::before {
- content: "/";
- margin: 0 var(--spacing-2xs);
- }
- }
- }
}
}
diff --git a/src/components/PostMeta/PostMeta.tsx b/src/components/PostMeta/PostMeta.tsx
index b951c44..e89e0e2 100644
--- a/src/components/PostMeta/PostMeta.tsx
+++ b/src/components/PostMeta/PostMeta.tsx
@@ -1,19 +1,24 @@
+import {
+ Author,
+ CommentsCount,
+ Dates,
+ PostsCount,
+ ReadingTime,
+ Thematics,
+ Topics,
+ Website,
+} from '@components/MetaItems';
+import { MetaKind } from '@ts/types/app';
import { ArticleMeta } from '@ts/types/articles';
-import { settings } from '@utils/config';
-import { getFormattedDate } from '@utils/helpers/format';
-import Link from 'next/link';
import { useRouter } from 'next/router';
-import { useIntl } from 'react-intl';
import styles from './PostMeta.module.scss';
-type PostMetaMode = 'list' | 'single';
-
const PostMeta = ({
meta,
- mode = 'list',
+ kind = 'list',
}: {
meta: ArticleMeta;
- mode?: PostMetaMode;
+ kind?: MetaKind;
}) => {
const {
author,
@@ -26,217 +31,34 @@ const PostMeta = ({
website,
wordsCount,
} = meta;
- const intl = useIntl();
- const router = useRouter();
- const locale = router.locale ? router.locale : settings.locales.defaultLocale;
- const isThematic = () => router.asPath.includes('/thematique/');
- const isArticle = () => router.asPath.includes('/article/');
-
- const getTopics = () => {
- return (
- topics &&
- topics.map((topic) => {
- return (
- <dd key={topic.id} className={styles.description}>
- <Link href={`/sujet/${topic.slug}`}>
- <a>{topic.title}</a>
- </Link>
- </dd>
- );
- })
- );
- };
-
- const getThematics = () => {
- return (
- thematics &&
- thematics.map((thematic) => {
- return (
- <dd key={thematic.id} className={styles.description}>
- <Link href={`/thematique/${thematic.slug}`}>
- <a>{thematic.title}</a>
- </Link>
- </dd>
- );
- })
- );
- };
-
- const getCommentsCount = () => {
- return intl.formatMessage(
- {
- defaultMessage:
- '{commentCount, plural, =0 {No comments} one {# comment} other {# comments}}',
- description: 'PostMeta: comment count value',
- },
- { commentCount }
- );
- };
-
- const getReadingTime = () => {
- if (!readingTime) return;
- if (readingTime < 0)
- return intl.formatMessage({
- defaultMessage: 'less than 1 minute',
- description: 'PostMeta: Reading time value',
- });
- return intl.formatMessage(
- {
- defaultMessage:
- '{readingTime, plural, =0 {# minutes} one {# minute} other {# minutes}}',
- description: 'PostMeta: reading time value',
- },
- { readingTime }
- );
- };
+ const { asPath } = useRouter();
+ const isThematic = () => asPath.includes('/thematique/');
- const getDates = () => {
- if (!dates) return <></>;
-
- const publicationDate = getFormattedDate(dates.publication, locale);
- const updateDate = getFormattedDate(dates.update, locale);
-
- return (
- <>
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'PostMeta: publication date label',
- })}
- </dt>
- <dd className={styles.description}>
- <time dateTime={dates.publication}>{publicationDate}</time>
- </dd>
- </div>
- {publicationDate !== updateDate && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'PostMeta: update date label',
- })}
- </dt>
- <dd className={styles.description}>
- <time dateTime={dates.update}>{updateDate}</time>
- </dd>
- </div>
- )}
- </>
- );
- };
-
- const wrapperClass = styles[`wrapper--${mode}`];
+ const wrapperClass = styles[`wrapper--${kind}`];
return (
<dl className={wrapperClass}>
- {author && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Written by:',
- description: 'Article meta',
- })}
- </dt>
- <dd className={styles.description}>{author.name}</dd>
- </div>
+ {author && <Author name={author.name} kind={kind} />}
+ {dates && (
+ <Dates
+ publication={dates.publication}
+ update={dates.update}
+ kind={kind}
+ />
)}
- {getDates()}
{readingTime !== undefined && wordsCount !== undefined && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Reading time:',
- description: 'Article meta',
- })}
- </dt>
- <dd
- className={styles.description}
- title={`Approximately ${wordsCount.toLocaleString(locale)} words`}
- >
- {getReadingTime()}
- </dd>
- </div>
- )}
- {results && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'Article meta',
- })}
- </dt>
- <dd className={styles.description}>
- {intl.formatMessage(
- {
- defaultMessage:
- '{results, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'PostMeta: total found articles',
- },
- { results }
- )}
- </dd>
- </div>
+ <ReadingTime time={readingTime} words={wordsCount} kind={kind} />
)}
+ {results && <PostsCount total={results} kind={kind} />}
{!isThematic() && thematics && thematics.length > 0 && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage(
- {
- defaultMessage:
- '{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}',
- description: 'PostMeta: thematics list label',
- },
- { thematicsCount: thematics.length }
- )}
- </dt>
- {getThematics()}
- </div>
+ <Thematics list={thematics} kind={kind} />
)}
{isThematic() && topics && topics.length > 0 && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage(
- {
- defaultMessage:
- '{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}',
- description: 'PostMeta: topics list label',
- },
- { topicsCount: topics.length }
- )}
- </dt>
- {getTopics()}
- </div>
- )}
- {website && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Website:',
- description: 'PostMeta: website label',
- })}
- </dt>
- <dd className={styles.description}>
- <a href={website}>{website}</a>
- </dd>
- </div>
+ <Topics list={topics} kind={kind} />
)}
+ {website && <Website url={website} kind={kind} />}
{commentCount !== undefined && (
- <div className={styles.item}>
- <dt className={styles.term}>
- {intl.formatMessage({
- defaultMessage: 'Comments:',
- description: 'PostMeta: comment count label',
- })}
- </dt>
- <dd className={styles.description}>
- {isArticle() ? (
- <a href="#comments">{getCommentsCount()}</a>
- ) : (
- getCommentsCount()
- )}
- </dd>
- </div>
+ <CommentsCount total={commentCount} kind={kind} />
)}
</dl>
);
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 9164b83..4928516 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -3,10 +3,6 @@
"defaultMessage": "Others topics",
"description": "TopicPage: topics list widget title"
},
- "+COyEW": {
- "defaultMessage": "{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}",
- "description": "PostMeta: topics list label"
- },
"+Dre5J": {
"defaultMessage": "Open-source projects",
"description": "CVPage: social media widget title"
@@ -31,6 +27,10 @@
"defaultMessage": "Failed to load.",
"description": "TopicsList: failed to load text"
},
+ "0pp/IQ": {
+ "defaultMessage": "{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}",
+ "description": "Topics: topics list meta label"
+ },
"0zBQpa": {
"defaultMessage": "Message",
"description": "ContactForm: message field label"
@@ -47,6 +47,10 @@
"defaultMessage": "Published on:",
"description": "RecentPosts: publication date label"
},
+ "1r4ujR": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}",
+ "description": "Thematics: thematics list meta label"
+ },
"2D9tB5": {
"defaultMessage": "Topics",
"description": "BlogPage: topics list widget title"
@@ -67,10 +71,22 @@
"defaultMessage": "Page not found.",
"description": "404Page: SEO - Meta description"
},
+ "4EMSLO": {
+ "defaultMessage": "{total, plural, =0 {No articles} one {# article} other {# articles}}",
+ "description": "PostCount: total found articles"
+ },
"4zAUSu": {
"defaultMessage": "Legal notice - {websiteName}",
"description": "LegalNoticePage: SEO - Page title"
},
+ "52Fev1": {
+ "defaultMessage": "Published on:",
+ "description": "Dates: publication date meta label"
+ },
+ "6BRtAu": {
+ "defaultMessage": "Comments:",
+ "description": "CommentsCount: comment count meta label"
+ },
"6dXfvr": {
"defaultMessage": "Table of Contents",
"description": "ProjectPage: ToC sidebar aria-label"
@@ -123,6 +139,10 @@
"defaultMessage": "Others formats",
"description": "CVPage: cv preview widget title"
},
+ "C+r/LF": {
+ "defaultMessage": "Updated on:",
+ "description": "Dates: update date meta label"
+ },
"C/XGkH": {
"defaultMessage": "Failed to load.",
"description": "BlogPage: failed to load text"
@@ -167,18 +187,6 @@
"defaultMessage": "All posts in {name}",
"description": "TopicPage: posts list title"
},
- "Fj8WFC": {
- "defaultMessage": "{results, plural, =0 {No articles} one {# article} other {# articles}}",
- "description": "PostMeta: total found articles"
- },
- "FtokGF": {
- "defaultMessage": "Updated on:",
- "description": "PostMeta: update date label"
- },
- "GUfnQ4": {
- "defaultMessage": "Reading time:",
- "description": "Article meta"
- },
"GgIWnN": {
"defaultMessage": "<a11y>Jump to </a11y>{title}",
"description": "ToC: link"
@@ -223,6 +231,10 @@
"defaultMessage": "Sidebar",
"description": "ArticlePage: right sidebar aria-label"
},
+ "JsOoAW": {
+ "defaultMessage": "Website:",
+ "description": "Website: website meta label"
+ },
"KERk7L": {
"defaultMessage": "Filter by:",
"description": "BlogPage: sidebar title"
@@ -247,10 +259,6 @@
"defaultMessage": "Projects",
"description": "HomePage: link to projects"
},
- "N7I4lC": {
- "defaultMessage": "less than 1 minute",
- "description": "PostMeta: Reading time value"
- },
"N804XO": {
"defaultMessage": "Topics",
"description": "SearchPage: topics list widget title"
@@ -287,10 +295,6 @@
"defaultMessage": "Email",
"description": "CommentForm: Email field label"
},
- "Ox/daH": {
- "defaultMessage": "{commentCount, plural, =0 {No comments} one {# comment} other {# comments}}",
- "description": "PostMeta: comment count value"
- },
"P0I+Xm": {
"defaultMessage": "Journal du hacker",
"description": "Sharing: Journal du hacker"
@@ -355,10 +359,6 @@
"defaultMessage": "Search",
"description": "SearchPage: breadcrumb item"
},
- "U++A+B": {
- "defaultMessage": "{readingTime, plural, =0 {# minutes} one {# minute} other {# minutes}}",
- "description": "PostMeta: reading time value"
- },
"U+35YD": {
"defaultMessage": "Search",
"description": "SearchPage: page title"
@@ -387,10 +387,6 @@
"defaultMessage": "Email",
"description": "ContactForm: email field label"
},
- "W2G95o": {
- "defaultMessage": "Comments:",
- "description": "PostMeta: comment count label"
- },
"WGFOmA": {
"defaultMessage": "Send",
"description": "CommentForm: Send button"
@@ -459,10 +455,6 @@
"defaultMessage": "{starsCount, plural, =0 {0 stars on Github} one {# star on Github} other {# stars on Github}}",
"description": "ProjectSummary: technologies list label"
},
- "agLf5v": {
- "defaultMessage": "Website:",
- "description": "PostMeta: website label"
- },
"akSutM": {
"defaultMessage": "Projects",
"description": "MainNav: projects link"
@@ -535,10 +527,6 @@
"defaultMessage": "Sidebar",
"description": "TopicPage: right sidebar aria-label"
},
- "fGnfqp": {
- "defaultMessage": "Published on:",
- "description": "PostMeta: publication date label"
- },
"fOe8rH": {
"defaultMessage": "Failed to load.",
"description": "SearchPage: failed to load text"
@@ -587,9 +575,9 @@
"defaultMessage": "Linux",
"description": "HomePage: link to Linux thematic"
},
- "jGqV2+": {
+ "jCyqZS": {
"defaultMessage": "Written by:",
- "description": "Article meta"
+ "description": "Author: article author meta label"
},
"jN+dY5": {
"defaultMessage": "Website",
@@ -599,9 +587,13 @@
"defaultMessage": "Resume",
"description": "MainNav: resume link"
},
- "l0+ROl": {
- "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}",
- "description": "PostMeta: thematics list label"
+ "k7/SkN": {
+ "defaultMessage": "Approximately {number} words",
+ "description": "ReadingTime: number of words"
+ },
+ "lKGNKx": {
+ "defaultMessage": "{total, plural, =0 {No comments} one {# comment} other {# comments}}",
+ "description": "CommentsCount: comment count value"
},
"lKZm9t": {
"defaultMessage": "Email",
@@ -619,6 +611,10 @@
"defaultMessage": "{title} preview",
"description": "ProjectSummary: cover alt text"
},
+ "n0Gbod": {
+ "defaultMessage": "Reading time:",
+ "description": "ReadingTime: reading time meta label"
+ },
"nFMdWI": {
"defaultMessage": "Dark Theme 🌙",
"description": "Prism: toggle dark theme button text"
@@ -643,6 +639,10 @@
"defaultMessage": "{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}",
"description": "ProjectPreview: technologies list label"
},
+ "p1zZ/Z": {
+ "defaultMessage": "Total:",
+ "description": "PostCount: total found articles meta label"
+ },
"pEtJik": {
"defaultMessage": "Load more?",
"description": "SearchPage: load more text"
@@ -723,10 +723,6 @@
"defaultMessage": "Popularity:",
"description": "ProjectSummary: popularity label"
},
- "vhIggb": {
- "defaultMessage": "Total:",
- "description": "Article meta"
- },
"vkF/RP": {
"defaultMessage": "Web development",
"description": "HomePage: link to web development thematic"
@@ -747,10 +743,18 @@
"defaultMessage": "Free",
"description": "HomePage: link to free thematic"
},
+ "wdqOpf": {
+ "defaultMessage": "{time, plural, =0 {# minutes} one {# minute} other {# minutes}}",
+ "description": "ReadingTime: reading time value"
+ },
"xC3Khf": {
"defaultMessage": "Download <link>CV in PDF</link>",
"description": "CVPreview: download as PDF link"
},
+ "ySsWZl": {
+ "defaultMessage": "less than 1 minute",
+ "description": "ReadingTime: Reading time value"
+ },
"yWjXRx": {
"defaultMessage": "Legal notice",
"description": "FooterNav: legal notice link"
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 478a9cf..645ffa9 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -3,10 +3,6 @@
"defaultMessage": "Autres sujets",
"description": "TopicPage: topics list widget title"
},
- "+COyEW": {
- "defaultMessage": "{topicsCount, plural, =0 {Sujets :} one {Sujet :} other {Sujets :}}",
- "description": "PostMeta: topics list label"
- },
"+Dre5J": {
"defaultMessage": "Projets open-source",
"description": "CVPage: social media widget title"
@@ -31,6 +27,10 @@
"defaultMessage": "Échec du chargement.",
"description": "TopicsList: failed to load text"
},
+ "0pp/IQ": {
+ "defaultMessage": "{topicsCount, plural, =0 {Sujet :} one {Sujet :} other {Sujets :}}",
+ "description": "Topics: topics list meta label"
+ },
"0zBQpa": {
"defaultMessage": "Message",
"description": "ContactForm: message field label"
@@ -47,6 +47,10 @@
"defaultMessage": "Publié le :",
"description": "RecentPosts: publication date label"
},
+ "1r4ujR": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Thématique :} one {Thématique :} other {Thématiques :}}",
+ "description": "Thematics: thematics list meta label"
+ },
"2D9tB5": {
"defaultMessage": "Sujets",
"description": "BlogPage: topics list widget title"
@@ -67,10 +71,22 @@
"defaultMessage": "Page non trouvée.",
"description": "404Page: SEO - Meta description"
},
+ "4EMSLO": {
+ "defaultMessage": "{total, plural, =0 {Aucun article} one {# article} other {# articles}}",
+ "description": "PostCount: total found articles"
+ },
"4zAUSu": {
"defaultMessage": "Mentions légales - {websiteName}",
"description": "LegalNoticePage: SEO - Page title"
},
+ "52Fev1": {
+ "defaultMessage": "Publié le :",
+ "description": "Dates: publication date meta label"
+ },
+ "6BRtAu": {
+ "defaultMessage": "Commentaires :",
+ "description": "CommentsCount: comment count meta label"
+ },
"6dXfvr": {
"defaultMessage": "Table des matières",
"description": "ProjectPage: ToC sidebar aria-label"
@@ -123,6 +139,10 @@
"defaultMessage": "Autres formats",
"description": "CVPage: cv preview widget title"
},
+ "C+r/LF": {
+ "defaultMessage": "Mis à jour le :",
+ "description": "Dates: update date meta label"
+ },
"C/XGkH": {
"defaultMessage": "Échec du chargement.",
"description": "BlogPage: failed to load text"
@@ -167,18 +187,6 @@
"defaultMessage": "Tous les articles dans {name}",
"description": "TopicPage: posts list title"
},
- "Fj8WFC": {
- "defaultMessage": "{results, plural, =0 {Aucun article} one {# article} other {# articles}}",
- "description": "PostMeta: total found articles"
- },
- "FtokGF": {
- "defaultMessage": "Mis à jour le :",
- "description": "PostMeta: update date label"
- },
- "GUfnQ4": {
- "defaultMessage": "Temps de lecture :",
- "description": "Article meta"
- },
"GgIWnN": {
"defaultMessage": "<a11y>Atteindre </a11y>{title}",
"description": "ToC: link"
@@ -223,6 +231,10 @@
"defaultMessage": "Barre latérale",
"description": "ArticlePage: right sidebar aria-label"
},
+ "JsOoAW": {
+ "defaultMessage": "Site web :",
+ "description": "Website: website meta label"
+ },
"KERk7L": {
"defaultMessage": "Filtrer par :",
"description": "BlogPage: sidebar title"
@@ -247,10 +259,6 @@
"defaultMessage": "Projets",
"description": "HomePage: link to projects"
},
- "N7I4lC": {
- "defaultMessage": "moins d'une minute",
- "description": "PostMeta: Reading time value"
- },
"N804XO": {
"defaultMessage": "Sujets",
"description": "SearchPage: topics list widget title"
@@ -287,10 +295,6 @@
"defaultMessage": "Email",
"description": "CommentForm: Email field label"
},
- "Ox/daH": {
- "defaultMessage": "{commentCount, plural, =0 {Aucun commentaire} one {# commentaire} other {# commentaires}}",
- "description": "PostMeta: comment count value"
- },
"P0I+Xm": {
"defaultMessage": "Journal du hacker",
"description": "Sharing: Journal du hacker"
@@ -355,10 +359,6 @@
"defaultMessage": "Recherche",
"description": "SearchPage: breadcrumb item"
},
- "U++A+B": {
- "defaultMessage": "{readingTime, plural, =0 {# minute} one {# minute} other {# minutes}}",
- "description": "PostMeta: reading time value"
- },
"U+35YD": {
"defaultMessage": "Recherche",
"description": "SearchPage: page title"
@@ -387,10 +387,6 @@
"defaultMessage": "Email",
"description": "ContactForm: email field label"
},
- "W2G95o": {
- "defaultMessage": "Commentaires :",
- "description": "PostMeta: comment count label"
- },
"WGFOmA": {
"defaultMessage": "Envoyer",
"description": "CommentForm: Send button"
@@ -459,10 +455,6 @@
"defaultMessage": "{starsCount, plural, =0 {0 étoile sur Github} one {# étoile sur Github} other {# étoiles sur Github}}",
"description": "ProjectSummary: technologies list label"
},
- "agLf5v": {
- "defaultMessage": "Site web :",
- "description": "PostMeta: website label"
- },
"akSutM": {
"defaultMessage": "Projets",
"description": "MainNav: projects link"
@@ -535,10 +527,6 @@
"defaultMessage": "Barre latérale",
"description": "TopicPage: right sidebar aria-label"
},
- "fGnfqp": {
- "defaultMessage": "Publié le :",
- "description": "PostMeta: publication date label"
- },
"fOe8rH": {
"defaultMessage": "Échec du chargement.",
"description": "SearchPage: failed to load text"
@@ -587,9 +575,9 @@
"defaultMessage": "Linux",
"description": "HomePage: link to Linux thematic"
},
- "jGqV2+": {
+ "jCyqZS": {
"defaultMessage": "Écrit par :",
- "description": "Article meta"
+ "description": "Author: article author meta label"
},
"jN+dY5": {
"defaultMessage": "Site web",
@@ -599,9 +587,13 @@
"defaultMessage": "CV",
"description": "MainNav: resume link"
},
- "l0+ROl": {
- "defaultMessage": "{thematicsCount, plural, =0 {Thématiques :} one {Thématique :} other {Thématiques :}}",
- "description": "PostMeta: thematics list label"
+ "k7/SkN": {
+ "defaultMessage": "Environ {number} mots",
+ "description": "ReadingTime: number of words"
+ },
+ "lKGNKx": {
+ "defaultMessage": "{total, plural, =0 {Aucun commentaire} one {# commentaire} other {# commentaires}}",
+ "description": "CommentsCount: comment count value"
},
"lKZm9t": {
"defaultMessage": "Email",
@@ -619,6 +611,10 @@
"defaultMessage": "Aperçu de {title}",
"description": "ProjectSummary: cover alt text"
},
+ "n0Gbod": {
+ "defaultMessage": "Temps de lecture :",
+ "description": "ReadingTime: reading time meta label"
+ },
"nFMdWI": {
"defaultMessage": "Thème sombre 🌙",
"description": "Prism: toggle dark theme button text"
@@ -643,6 +639,10 @@
"defaultMessage": "{count, plural, =0 {Technologies :} one {Technologie :} other {Technologies :}}",
"description": "ProjectPreview: technologies list label"
},
+ "p1zZ/Z": {
+ "defaultMessage": "Total :",
+ "description": "PostCount: total found articles meta label"
+ },
"pEtJik": {
"defaultMessage": "En charger plus ?",
"description": "SearchPage: load more text"
@@ -723,10 +723,6 @@
"defaultMessage": "Popularité :",
"description": "ProjectSummary: popularity label"
},
- "vhIggb": {
- "defaultMessage": "Total :",
- "description": "Article meta"
- },
"vkF/RP": {
"defaultMessage": "Développement web",
"description": "HomePage: link to web development thematic"
@@ -747,10 +743,18 @@
"defaultMessage": "Libre",
"description": "HomePage: link to free thematic"
},
+ "wdqOpf": {
+ "defaultMessage": "{time, plural, =0 {# minute} one {# minute} other {# minutes}}",
+ "description": "ReadingTime: reading time value"
+ },
"xC3Khf": {
"defaultMessage": "Télécharger le <link>CV au format PDF</link>",
"description": "CVPreview: download as PDF link"
},
+ "ySsWZl": {
+ "defaultMessage": "moins d'une minute",
+ "description": "ReadingTime: Reading time value"
+ },
"yWjXRx": {
"defaultMessage": "Mentions légales",
"description": "FooterNav: legal notice link"
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 6d0ad5a..656f7c9 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -22,7 +22,7 @@ import { usePrismTheme } from '@utils/providers/prism';
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Prism from 'prismjs';
+import { highlightAll } from 'prismjs';
import { ParsedUrlQuery } from 'querystring';
import { useEffect } from 'react';
import { useIntl } from 'react-intl';
@@ -39,7 +39,7 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({
useEffect(() => {
addPrismClasses();
- Prism.highlightAll();
+ highlightAll();
});
const { setCodeBlocks } = usePrismTheme();
diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts
index 0960cbd..444733c 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -99,6 +99,8 @@ export type Meta = {
updatedOn: string;
};
+export type MetaKind = 'article' | 'list';
+
export type NoticeType = 'error' | 'info' | 'success' | 'warning';
export type PageInfo = {