summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-01-29 18:21:37 +0100
committerArmand Philippot <git@armandphilippot.com>2022-01-29 19:02:57 +0100
commite4d5b8151802517b2943756fc0d09ffa95e2c4e2 (patch)
tree9e99137a7b64ea7993a8311a7162336a551be8b2 /src/components
parent47b854de26dea24e7838fd0804df103dee99635f (diff)
chore: replace lingui functions with react-intl
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Branding/Branding.tsx24
-rw-r--r--src/components/Breadcrumb/Breadcrumb.tsx46
-rw-r--r--src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx27
-rw-r--r--src/components/Comment/Comment.tsx39
-rw-r--r--src/components/CommentForm/CommentForm.tsx45
-rw-r--r--src/components/CommentsList/CommentsList.tsx20
-rw-r--r--src/components/Footer/Footer.tsx10
-rw-r--r--src/components/FooterNav/FooterNav.tsx18
-rw-r--r--src/components/Icons/Copyright/Copyright.tsx3
-rw-r--r--src/components/Icons/Moon/Moon.tsx11
-rw-r--r--src/components/Icons/Sun/Sun.tsx11
-rw-r--r--src/components/Layouts/Layout.tsx14
-rw-r--r--src/components/MDX/CodeBlock/CodeBlock.tsx6
-rw-r--r--src/components/MainNav/MainNav.tsx66
-rw-r--r--src/components/PaginationCursor/PaginationCursor.tsx21
-rw-r--r--src/components/PostFooter/PostFooter.tsx11
-rw-r--r--src/components/PostMeta/PostMeta.tsx118
-rw-r--r--src/components/PostPreview/PostPreview.tsx31
-rw-r--r--src/components/PostsList/PostsList.tsx25
-rw-r--r--src/components/ProjectPreview/ProjectPreview.tsx22
-rw-r--r--src/components/ProjectSummary/ProjectSummary.tsx68
-rw-r--r--src/components/SearchForm/SearchForm.tsx17
-rw-r--r--src/components/Settings/ReduceMotion/ReduceMotion.tsx18
-rw-r--r--src/components/Settings/Settings.tsx10
-rw-r--r--src/components/Settings/ThemeToggle/ThemeToggle.tsx8
-rw-r--r--src/components/Spinner/Spinner.tsx11
-rw-r--r--src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx13
-rw-r--r--src/components/Widgets/CVPreview/CVPreview.tsx16
-rw-r--r--src/components/Widgets/RecentPosts/RecentPosts.tsx16
-rw-r--r--src/components/Widgets/RelatedThematics/RelatedThematics.tsx12
-rw-r--r--src/components/Widgets/RelatedTopics/RelatedTopics.tsx12
-rw-r--r--src/components/Widgets/Sharing/Sharing.tsx42
-rw-r--r--src/components/Widgets/ThematicsList/ThematicsList.tsx13
-rw-r--r--src/components/Widgets/ToC/ToC.tsx8
-rw-r--r--src/components/Widgets/TopicsList/TopicsList.tsx13
35 files changed, 677 insertions, 168 deletions
diff --git a/src/components/Branding/Branding.tsx b/src/components/Branding/Branding.tsx
index 01948e9..efb3a49 100644
--- a/src/components/Branding/Branding.tsx
+++ b/src/components/Branding/Branding.tsx
@@ -1,17 +1,18 @@
-import Image from 'next/image';
-import Link from 'next/link';
-import { ReactElement } from 'react';
-import { t } from '@lingui/macro';
import photo from '@assets/images/armand-philippot.jpg';
import { config } from '@config/website';
-import styles from './Branding.module.scss';
import Head from 'next/head';
+import Image from 'next/image';
+import Link from 'next/link';
+import { ReactElement } from 'react';
+import { useIntl } from 'react-intl';
import { Person, WithContext } from 'schema-dts';
+import styles from './Branding.module.scss';
import Logo from './Logo/Logo';
type BrandingReturn = ({ isHome }: { isHome: boolean }) => ReactElement;
const Branding: BrandingReturn = ({ isHome = false }) => {
+ const intl = useIntl();
const TitleTag = isHome ? 'h1' : 'p';
const schemaJsonLd: WithContext<Person> = {
@@ -38,10 +39,15 @@ const Branding: BrandingReturn = ({ isHome = false }) => {
<div className={styles.logo__front}>
<Image
src={photo}
- alt={t({
- message: `${config.name} picture`,
- comment: 'Branding logo.',
- })}
+ alt={intl.formatMessage(
+ {
+ defaultMessage: '{brandingName} picture',
+ description: 'Branding: branding name picture.',
+ },
+ {
+ brandingName: config.name,
+ }
+ )}
layout="responsive"
/>
</div>
diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx
index 7c8eb5c..30179be 100644
--- a/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/src/components/Breadcrumb/Breadcrumb.tsx
@@ -1,12 +1,13 @@
import { config } from '@config/website';
-import { t } from '@lingui/macro';
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import { BreadcrumbList, WithContext } from 'schema-dts';
import styles from './Breadcrumb.module.scss';
const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
+ const intl = useIntl();
const router = useRouter();
const isHome = router.pathname === '/';
@@ -20,14 +21,24 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
<>
<li className={styles.item}>
<Link href="/">
- <a>{t`Home`}</a>
+ <a>
+ {intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'Breadcrumb: Home item',
+ })}
+ </a>
</Link>
</li>
{(isArticle || isThematic || isSubject) && (
<>
<li className={styles.item}>
<Link href="/blog">
- <a>{t`Blog`}</a>
+ <a>
+ {intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'Breadcrumb: Blog item',
+ })}
+ </a>
</Link>
</li>
</>
@@ -36,7 +47,12 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
<>
<li className={styles.item}>
<Link href="/projets">
- <a>{t`Projects`}</a>
+ <a>
+ {intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'Breadcrumb: Projects item',
+ })}
+ </a>
</Link>
</li>
</>
@@ -51,7 +67,10 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
const homepage: BreadcrumbList['itemListElement'] = {
'@type': 'ListItem',
position: 1,
- name: t`Home`,
+ name: intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'Breadcrumb: Home item',
+ }),
item: config.url,
};
@@ -61,7 +80,10 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
const blog: BreadcrumbList['itemListElement'] = {
'@type': 'ListItem',
position: 2,
- name: t`Blog`,
+ name: intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'Breadcrumb: Blog item',
+ }),
item: `${config.url}/blog`,
};
@@ -72,7 +94,10 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
const blog: BreadcrumbList['itemListElement'] = {
'@type': 'ListItem',
position: 2,
- name: t`Projects`,
+ name: intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'Breadcrumb: Projects item',
+ }),
item: `${config.url}/projets`,
};
@@ -108,7 +133,12 @@ const Breadcrumb = ({ pageTitle }: { pageTitle: string }) => {
</Head>
{!isHome && (
<nav id="breadcrumb" className={styles.wrapper}>
- <span className="screen-reader-text">{t`You are here:`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage({
+ defaultMessage: 'You are here:',
+ description: 'Breadcrumb: You are here prefix',
+ })}
+ </span>
<ol className={styles.list}>{getItems()}</ol>
</nav>
)}
diff --git a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx b/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx
index 246ad80..e9f6079 100644
--- a/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx
+++ b/src/components/Buttons/ButtonToolbar/ButtonToolbar.tsx
@@ -1,6 +1,6 @@
import { CloseIcon, CogIcon, SearchIcon } from '@components/Icons';
-import { t } from '@lingui/macro';
import { ForwardedRef, forwardRef, SetStateAction } from 'react';
+import { useIntl } from 'react-intl';
import styles from '../Buttons.module.scss';
type ButtonType = 'search' | 'settings';
@@ -17,6 +17,7 @@ const ButtonToolbar = (
},
ref: ForwardedRef<HTMLButtonElement>
) => {
+ const intl = useIntl();
const ButtonIcon = () => (type === 'search' ? <SearchIcon /> : <CogIcon />);
const btnClasses = isActivated
? `${styles.toolbar} ${styles['toolbar--activated']}`
@@ -38,9 +39,29 @@ const ButtonToolbar = (
</span>
</span>
{isActivated ? (
- <span className="screen-reader-text">{t`Close ${type}`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage(
+ {
+ defaultMessage: 'Close {type}',
+ description: 'ButtonToolbar: Close button',
+ },
+ {
+ type,
+ }
+ )}
+ </span>
) : (
- <span className="screen-reader-text">{t`Open ${type}`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage(
+ {
+ defaultMessage: 'Open {type}',
+ description: 'ButtonToolbar: Open button',
+ },
+ {
+ type,
+ }
+ )}
+ </span>
)}
</button>
);
diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx
index 6eb0184..e95a378 100644
--- a/src/components/Comment/Comment.tsx
+++ b/src/components/Comment/Comment.tsx
@@ -1,7 +1,6 @@
import { Button } from '@components/Buttons';
import CommentForm from '@components/CommentForm/CommentForm';
import { config } from '@config/website';
-import { t } from '@lingui/macro';
import { Comment as CommentData } from '@ts/types/comments';
import { getFormattedDate } from '@utils/helpers/format';
import Head from 'next/head';
@@ -9,6 +8,7 @@ import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';
+import { useIntl } from 'react-intl';
import { Comment as CommentSchema, WithContext } from 'schema-dts';
import styles from './Comment.module.scss';
@@ -21,6 +21,7 @@ const Comment = ({
comment: CommentData;
isNested?: boolean;
}) => {
+ const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : config.locales.defaultLocale;
const [isReply, setIsReply] = useState<boolean>(false);
@@ -48,7 +49,16 @@ const Comment = ({
minute: 'numeric',
})
.replace(':', 'h');
- return t`${date} at ${time}`;
+ return intl.formatMessage(
+ {
+ defaultMessage: '{date} at {time}',
+ description: 'Comment: publication date',
+ },
+ {
+ date,
+ time,
+ }
+ );
};
const getApprovedComment = () => {
@@ -68,7 +78,12 @@ const Comment = ({
{getCommentAuthor()}
</header>
<dl className={styles.date}>
- <dt>{t`Published on:`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Published on:',
+ description: 'Comment: publication date label',
+ })}
+ </dt>
<dd>
<time dateTime={comment.date}>
<Link href={`#comment-${comment.commentId}`}>
@@ -83,9 +98,12 @@ const Comment = ({
></div>
{!isNested && (
<footer className={styles.footer}>
- <Button
- clickHandler={() => setIsReply((prev) => !prev)}
- >{t`Reply`}</Button>
+ <Button clickHandler={() => setIsReply((prev) => !prev)}>
+ {intl.formatMessage({
+ defaultMessage: 'Reply',
+ description: 'Comment: reply button',
+ })}
+ </Button>
</footer>
)}
</article>
@@ -116,7 +134,14 @@ const Comment = ({
};
const getCommentStatus = () => {
- return <p>{t`This comment is awaiting moderation.`}</p>;
+ return (
+ <p>
+ {intl.formatMessage({
+ defaultMessage: 'This comment is awaiting moderation.',
+ description: 'Comment: awaiting moderation message',
+ })}
+ </p>
+ );
};
const schemaJsonLd: WithContext<CommentSchema> = {
diff --git a/src/components/CommentForm/CommentForm.tsx b/src/components/CommentForm/CommentForm.tsx
index 1ed219c..0ea3276 100644
--- a/src/components/CommentForm/CommentForm.tsx
+++ b/src/components/CommentForm/CommentForm.tsx
@@ -1,9 +1,9 @@
import { ButtonSubmit } from '@components/Buttons';
import { Form, FormItem, Input, TextArea } from '@components/Form';
import Notice from '@components/Notice/Notice';
-import { t } from '@lingui/macro';
import { createComment } from '@services/graphql/mutations';
import { ForwardedRef, forwardRef, useState } from 'react';
+import { useIntl } from 'react-intl';
import styles from './CommentForm.module.scss';
const CommentForm = (
@@ -18,6 +18,7 @@ const CommentForm = (
},
ref: ForwardedRef<HTMLInputElement>
) => {
+ const intl = useIntl();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [website, setWebsite] = useState('');
@@ -68,7 +69,12 @@ const CommentForm = (
return (
<div className={wrapperClasses}>
- <h2 className={styles.title}>{t`Leave a comment`}</h2>
+ <h2 className={styles.title}>
+ {intl.formatMessage({
+ defaultMessage: 'Leave a comment',
+ description: 'CommentForm: form title',
+ })}
+ </h2>
<Form
submitHandler={submitHandler}
modifier={isReply ? 'centered' : undefined}
@@ -77,7 +83,10 @@ const CommentForm = (
<Input
id="commenter-name"
name="commenter-name"
- label={t`Name`}
+ label={intl.formatMessage({
+ defaultMessage: 'Name',
+ description: 'CommentForm: Name field label',
+ })}
required={true}
value={name}
setValue={setName}
@@ -88,7 +97,10 @@ const CommentForm = (
<Input
id="commenter-email"
name="commenter-email"
- label={t`Email`}
+ label={intl.formatMessage({
+ defaultMessage: 'Email',
+ description: 'CommentForm: Email field label',
+ })}
required={true}
value={email}
setValue={setEmail}
@@ -98,7 +110,10 @@ const CommentForm = (
<Input
id="commenter-website"
name="commenter-website"
- label={t`Website`}
+ label={intl.formatMessage({
+ defaultMessage: 'Website',
+ description: 'CommentForm: Website field label',
+ })}
value={website}
setValue={setWebsite}
/>
@@ -107,17 +122,31 @@ const CommentForm = (
<TextArea
id="commenter-message"
name="commenter-message"
- label={t`Comment`}
+ label={intl.formatMessage({
+ defaultMessage: 'Comment',
+ description: 'CommentForm: Comment field label',
+ })}
value={message}
setValue={setMessage}
required={true}
/>
</FormItem>
<FormItem>
- <ButtonSubmit>{t`Send`}</ButtonSubmit>
+ <ButtonSubmit>
+ {intl.formatMessage({
+ defaultMessage: 'Send',
+ description: 'CommentForm: Send button',
+ })}
+ </ButtonSubmit>
</FormItem>
{isSuccess && !isApproved && (
- <Notice type="success">{t`Thanks for your comment! It is now awaiting moderation.`}</Notice>
+ <Notice type="success">
+ {intl.formatMessage({
+ defaultMessage:
+ 'Thanks for your comment! It is now awaiting moderation.',
+ description: 'CommentForm: Comment sent success message',
+ })}
+ </Notice>
)}
</Form>
</div>
diff --git a/src/components/CommentsList/CommentsList.tsx b/src/components/CommentsList/CommentsList.tsx
index bdca00b..6630a03 100644
--- a/src/components/CommentsList/CommentsList.tsx
+++ b/src/components/CommentsList/CommentsList.tsx
@@ -1,6 +1,6 @@
-import { Comment as CommentData } from '@ts/types/comments';
import Comment from '@components/Comment/Comment';
-import { t } from '@lingui/macro';
+import { Comment as CommentData } from '@ts/types/comments';
+import { useIntl } from 'react-intl';
import styles from './CommentsList.module.scss';
const CommentsList = ({
@@ -10,6 +10,8 @@ const CommentsList = ({
articleId: number;
comments: CommentData[];
}) => {
+ const intl = useIntl();
+
const getCommentsList = () => {
return comments.map((comment) => {
return (
@@ -20,11 +22,21 @@ const CommentsList = ({
return (
<>
- <h2 className={styles.title}>{t`Comments`}</h2>
+ <h2 className={styles.title}>
+ {intl.formatMessage({
+ defaultMessage: 'Comments',
+ description: 'CommentsList: Comments section title',
+ })}
+ </h2>
{comments.length > 0 ? (
<ol className={styles.list}>{getCommentsList()}</ol>
) : (
- <p className={styles['no-comments']}>{t`No comments yet.`}</p>
+ <p className={styles['no-comments']}>
+ {intl.formatMessage({
+ defaultMessage: 'No comments yet.',
+ description: 'CommentsList: No comment message',
+ })}
+ </p>
)}
</>
);
diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx
index 15a4660..4aa980d 100644
--- a/src/components/Footer/Footer.tsx
+++ b/src/components/Footer/Footer.tsx
@@ -2,11 +2,12 @@ import { ButtonLink } from '@components/Buttons';
import Copyright from '@components/Copyright/Copyright';
import FooterNav from '@components/FooterNav/FooterNav';
import { ArrowIcon } from '@components/Icons';
-import { t } from '@lingui/macro';
import { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
import styles from './Footer.module.scss';
const Footer = () => {
+ const intl = useIntl();
const [backToTopClasses, setBackToTopClasses] = useState(
`${styles['back-to-top']} ${styles['back-to-top--hidden']}`
);
@@ -36,7 +37,12 @@ const Footer = () => {
<FooterNav />
<div className={backToTopClasses}>
<ButtonLink target="#top" position="center">
- <span className="screen-reader-text">{t`Back to top`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage({
+ defaultMessage: 'Back to top',
+ description: 'Footer: Back to top button',
+ })}
+ </span>
<ArrowIcon direction="top" />
</ButtonLink>
</div>
diff --git a/src/components/FooterNav/FooterNav.tsx b/src/components/FooterNav/FooterNav.tsx
index 7266e7e..918fed7 100644
--- a/src/components/FooterNav/FooterNav.tsx
+++ b/src/components/FooterNav/FooterNav.tsx
@@ -1,9 +1,23 @@
import Link from 'next/link';
import styles from './FooterNav.module.scss';
-import { footerNav } from '@config/nav';
+import { NavItem } from '@ts/types/nav';
+import { useIntl } from 'react-intl';
const FooterNav = () => {
- const navItems = footerNav.map((item) => {
+ const intl = useIntl();
+
+ const footerNavConfig: NavItem[] = [
+ {
+ id: 'legal-notice',
+ name: intl.formatMessage({
+ defaultMessage: 'Legal notice',
+ description: 'FooterNav: legal notice link',
+ }),
+ slug: '/mentions-legales',
+ },
+ ];
+
+ const navItems = footerNavConfig.map((item) => {
return (
<li key={item.id} className={styles.item}>
<Link href={item.slug}>
diff --git a/src/components/Icons/Copyright/Copyright.tsx b/src/components/Icons/Copyright/Copyright.tsx
index 396c127..d27c042 100644
--- a/src/components/Icons/Copyright/Copyright.tsx
+++ b/src/components/Icons/Copyright/Copyright.tsx
@@ -1,4 +1,3 @@
-import { t } from '@lingui/macro';
import styles from './Copyright.module.scss';
const CopyrightIcon = () => {
@@ -8,7 +7,7 @@ const CopyrightIcon = () => {
viewBox="0 0 211.99811 63.999996"
xmlns="http://www.w3.org/2000/svg"
>
- <title>{t`CC BY SA`}</title>
+ <title>CC BY SA</title>
<path d="m 175.53911,15.829498 c 0,-3.008 1.485,-4.514 4.458,-4.514 2.973,0 4.457,1.504 4.457,4.514 0,2.971 -1.486,4.457 -4.457,4.457 -2.971,0 -4.458,-1.486 -4.458,-4.457 z" />
<path d="m 188.62611,24.057498 v 13.085 h -3.656 v 15.542 h -9.944 v -15.541 h -3.656 v -13.086 c 0,-0.572 0.2,-1.057 0.599,-1.457 0.401,-0.399 0.887,-0.6 1.457,-0.6 h 13.144 c 0.533,0 1.01,0.2 1.428,0.6 0.417,0.4 0.628,0.886 0.628,1.457 z" />
<path d="m 179.94147,-1.9073486e-6 c -8.839,0 -16.34167,3.0848125073486 -22.51367,9.2578125073486 -6.285,6.4000004 -9.42969,13.9811874 -9.42969,22.7421874 0,8.762 3.14469,16.284312 9.42969,22.570312 6.361,6.286 13.86467,9.429688 22.51367,9.429688 8.799,0 16.43611,-3.181922 22.91211,-9.544922 6.096,-5.98 9.14453,-13.464078 9.14453,-22.455078 0,-8.952 -3.10646,-16.532188 -9.31446,-22.7421874 -6.172,-6.172 -13.75418,-9.2578125073486 -22.74218,-9.2578125073486 z M 180.05475,5.7714825 c 7.238,0 13.40967,2.55225 18.51367,7.6562495 5.103,5.106 7.65625,11.294313 7.65625,18.570313 0,7.391 -2.51397,13.50575 -7.54297,18.34375 -5.295,5.221 -11.50591,7.828125 -18.6289,7.828125 -7.162,0 -13.33268,-2.589484 -18.51368,-7.771484 -5.18,-5.178001 -7.76953,-11.310485 -7.76953,-18.396485 0,-7.047 2.60813,-13.238266 7.82813,-18.572265 5.029,-5.1040004 11.18103,-7.6582035 18.45703,-7.6582035 z" />
diff --git a/src/components/Icons/Moon/Moon.tsx b/src/components/Icons/Moon/Moon.tsx
index 62e7203..acdf6ae 100644
--- a/src/components/Icons/Moon/Moon.tsx
+++ b/src/components/Icons/Moon/Moon.tsx
@@ -1,14 +1,21 @@
-import { t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
import styles from './Moon.module.scss';
const MoonIcon = () => {
+ const intl = useIntl();
+
return (
<svg
className={styles.moon}
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
- <title>{t`Dark theme`}</title>
+ <title>
+ {intl.formatMessage({
+ defaultMessage: 'Dark theme',
+ description: 'Icons: Moon icon (dark theme)',
+ })}
+ </title>
<path d="M 51.077315,1.9893942 A 43.319985,43.319985 0 0 1 72.840039,39.563145 43.319985,43.319985 0 0 1 29.520053,82.88313 43.319985,43.319985 0 0 1 5.4309911,75.569042 48.132997,48.132997 0 0 0 46.126047,98 48.132997,48.132997 0 0 0 94.260004,49.867002 48.132997,48.132997 0 0 0 51.077315,1.9893942 Z" />
</svg>
);
diff --git a/src/components/Icons/Sun/Sun.tsx b/src/components/Icons/Sun/Sun.tsx
index 612d3fa..44945c2 100644
--- a/src/components/Icons/Sun/Sun.tsx
+++ b/src/components/Icons/Sun/Sun.tsx
@@ -1,14 +1,21 @@
-import { t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
import styles from './Sun.module.scss';
const SunIcon = () => {
+ const intl = useIntl();
+
return (
<svg
className={styles.sun}
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
- <title>{t`Light theme`}</title>
+ <title>
+ {intl.formatMessage({
+ defaultMessage: 'Light theme',
+ description: 'Icons: Sun icon (light theme)',
+ })}
+ </title>
<path d="M 69.398043,50.000437 A 19.399259,19.399204 0 0 1 49.998784,69.399641 19.399259,19.399204 0 0 1 30.599525,50.000437 19.399259,19.399204 0 0 1 49.998784,30.601234 19.399259,19.399204 0 0 1 69.398043,50.000437 Z m 27.699233,1.125154 c 2.657696,0.0679 1.156196,12.061455 -1.435545,11.463959 L 80.113224,59.000697 c -2.589801,-0.597494 -1.625657,-8.345536 1.032041,-8.278609 z m -18.06653,37.251321 c 1.644087,2.091234 -9.030355,8.610337 -10.126414,6.188346 L 62.331863,80.024585 c -1.096058,-2.423931 5.197062,-6.285342 6.839209,-4.194107 z M 38.611418,97.594444 C 38.02653,100.18909 26.24148,95.916413 27.436475,93.54001 l 7.168026,-14.256474 c 1.194024,-2.376403 8.102101,0.151313 7.517214,2.744986 z M 6.1661563,71.834242 C 3.7916868,73.028262 -0.25499873,61.16274 2.3386824,60.577853 L 17.905618,57.067567 c 2.593681,-0.584886 4.894434,6.403678 2.518995,7.598668 z M 6.146757,30.055146 c -2.3764094,-1.194991 4.46571,-11.714209 6.479353,-9.97798 l 12.090589,10.414462 c 2.014613,1.736229 -1.937017,7.926514 -4.314396,6.731524 z M 38.56777,4.2639045 C 37.982883,1.6682911 50.480855,0.41801247 50.415868,3.0766733 L 50.020123,19.028638 c -0.06596,2.657691 -7.357169,3.394862 -7.943027,0.800218 z m 40.403808,9.1622435 c 1.635357,-2.098023 10.437771,6.872168 8.339742,8.506552 l -12.58818,9.805327 c -2.099,1.634383 -7.192276,-3.626682 -5.557888,-5.724706 z M 97.096306,50.69105 c 2.657696,-0.06596 1.164926,12.462047 -1.425846,11.863582 L 80.122924,58.96578 c -2.590771,-0.597496 -1.636327,-7.814 1.021371,-7.879957 z" />
</svg>
);
diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx
index 599cfe2..b479ef3 100644
--- a/src/components/Layouts/Layout.tsx
+++ b/src/components/Layouts/Layout.tsx
@@ -1,12 +1,12 @@
-import { ReactElement, ReactNode, useEffect, useRef } from 'react';
import Footer from '@components/Footer/Footer';
import Header from '@components/Header/Header';
import Main from '@components/Main/Main';
import Breadcrumb from '@components/Breadcrumb/Breadcrumb';
-import { t } from '@lingui/macro';
-import Head from 'next/head';
import { config } from '@config/website';
+import Head from 'next/head';
import { useRouter } from 'next/router';
+import { ReactElement, ReactNode, useEffect, useRef } from 'react';
+import { useIntl } from 'react-intl';
import { WebSite, WithContext } from 'schema-dts';
const Layout = ({
@@ -16,6 +16,7 @@ const Layout = ({
children: ReactNode;
isHome?: boolean;
}) => {
+ const intl = useIntl();
const ref = useRef<HTMLSpanElement>(null);
const { asPath } = useRouter();
@@ -91,7 +92,12 @@ const Layout = ({
></script>
</Head>
<span ref={ref} tabIndex={-1} />
- <a href="#main" className="screen-reader-text">{t`Skip to content`}</a>
+ <a href="#main" className="screen-reader-text">
+ {intl.formatMessage({
+ defaultMessage: 'Skip to content',
+ description: 'Layout: Skip to content button',
+ })}
+ </a>
<Header isHome={isHome} />
<Main>{children}</Main>
<Footer />
diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx
index ef8b587..59386af 100644
--- a/src/components/MDX/CodeBlock/CodeBlock.tsx
+++ b/src/components/MDX/CodeBlock/CodeBlock.tsx
@@ -3,6 +3,7 @@ import { translateCopyButton } from '@utils/helpers/prism';
import { useRouter } from 'next/router';
import Prism from 'prismjs';
import { ReactChildren, useEffect } from 'react';
+import { useIntl } from 'react-intl';
const CodeBlock = ({
className,
@@ -15,6 +16,7 @@ const CodeBlock = ({
const languageClass = classNames.find((name: string) =>
name.startsWith('language-')
);
+ const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : config.locales.defaultLocale;
@@ -23,8 +25,8 @@ const CodeBlock = ({
});
useEffect(() => {
- translateCopyButton(locale);
- }, [locale]);
+ translateCopyButton(locale, intl);
+ }, [intl, locale]);
return (
<div>
diff --git a/src/components/MainNav/MainNav.tsx b/src/components/MainNav/MainNav.tsx
index afc4193..a866b9c 100644
--- a/src/components/MainNav/MainNav.tsx
+++ b/src/components/MainNav/MainNav.tsx
@@ -1,6 +1,3 @@
-import { SetStateAction } from 'react';
-import Link from 'next/link';
-import { t } from '@lingui/macro';
import {
BlogIcon,
ContactIcon,
@@ -9,9 +6,12 @@ import {
HomeIcon,
ProjectsIcon,
} from '@components/Icons';
-import { mainNav } from '@config/nav';
-import styles from './MainNav.module.scss';
+import { NavItem } from '@ts/types/nav';
+import Link from 'next/link';
import { useRouter } from 'next/router';
+import { SetStateAction } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './MainNav.module.scss';
const MainNav = ({
isOpened,
@@ -20,8 +20,52 @@ const MainNav = ({
isOpened: boolean;
setIsOpened: (value: SetStateAction<boolean>) => void;
}) => {
+ const intl = useIntl();
const router = useRouter();
+ const mainNavConfig: NavItem[] = [
+ {
+ id: 'home',
+ name: intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'MainNav: home link',
+ }),
+ slug: '/',
+ },
+ {
+ id: 'blog',
+ name: intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'MainNav: blog link',
+ }),
+ slug: '/blog',
+ },
+ {
+ id: 'projects',
+ name: intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'MainNav: projects link',
+ }),
+ slug: '/projets',
+ },
+ {
+ id: 'cv',
+ name: intl.formatMessage({
+ defaultMessage: 'Resume',
+ description: 'MainNav: resume link',
+ }),
+ slug: '/cv',
+ },
+ {
+ id: 'contact',
+ name: intl.formatMessage({
+ defaultMessage: 'Contact',
+ description: 'MainNav: contact link',
+ }),
+ slug: '/contact',
+ },
+ ];
+
const getIcon = (id: string) => {
switch (id) {
case 'home':
@@ -39,7 +83,7 @@ const MainNav = ({
}
};
- const navItems = mainNav.map((item) => {
+ const navItems = mainNavConfig.map((item) => {
const currentClass = router.asPath === item.slug ? styles.current : '';
return (
@@ -73,7 +117,15 @@ const MainNav = ({
>
<HamburgerIcon isActive={isOpened} />
<span className="screen-reader-text">
- {isOpened ? t`Close menu` : t`Open menu`}
+ {isOpened
+ ? intl.formatMessage({
+ defaultMessage: 'Close menu',
+ description: 'MainNav: close button',
+ })
+ : intl.formatMessage({
+ defaultMessage: 'Open menu',
+ description: 'MainNav: open button',
+ })}
</span>
</label>
<nav className={styles.nav}>
diff --git a/src/components/PaginationCursor/PaginationCursor.tsx b/src/components/PaginationCursor/PaginationCursor.tsx
index bcbb555..a8c6265 100644
--- a/src/components/PaginationCursor/PaginationCursor.tsx
+++ b/src/components/PaginationCursor/PaginationCursor.tsx
@@ -1,4 +1,4 @@
-import { plural, t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
import styles from './PaginationCursor.module.scss';
const PaginationCursor = ({
@@ -8,6 +8,8 @@ const PaginationCursor = ({
current: number;
total: number;
}) => {
+ const intl = useIntl();
+
return (
<div className={styles.wrapper}>
<progress
@@ -16,12 +18,19 @@ const PaginationCursor = ({
value={current}
aria-valuemin={0}
aria-valuemax={total}
- aria-label={t`Number of articles loaded out of the total available.`}
- title={plural(current, {
- zero: `# articles out of a total of ${total}`,
- one: `# article out of a total of ${total}`,
- other: `# articles out of a total of ${total}`,
+ aria-label={intl.formatMessage({
+ defaultMessage:
+ 'Number of articles loaded out of the total available.',
+ description: 'PaginationCursor: loaded articles count aria-label',
})}
+ title={intl.formatMessage(
+ {
+ defaultMessage:
+ '{articlesCount, plural, =0 {# articles} one {# article} other {# articles}} out of a total of {total}',
+ description: 'PaginationCursor: loaded articles count message',
+ },
+ { articlesCount: current, total }
+ )}
></progress>
</div>
);
diff --git a/src/components/PostFooter/PostFooter.tsx b/src/components/PostFooter/PostFooter.tsx
index ad471eb..6c97ec2 100644
--- a/src/components/PostFooter/PostFooter.tsx
+++ b/src/components/PostFooter/PostFooter.tsx
@@ -1,10 +1,12 @@
import { ButtonLink } from '@components/Buttons';
-import { t } from '@lingui/macro';
import { TopicPreview } from '@ts/types/taxonomies';
import Image from 'next/image';
+import { useIntl } from 'react-intl';
import styles from './PostFooter.module.scss';
const PostFooter = ({ topics }: { topics: TopicPreview[] }) => {
+ const intl = useIntl();
+
const getTopics = () => {
return topics.map((topic) => {
return (
@@ -31,7 +33,12 @@ const PostFooter = ({ topics }: { topics: TopicPreview[] }) => {
{topics.length > 0 && (
<>
<dl className={styles.meta}>
- <dt>{t`Read more articles about:`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Read more articles about:',
+ description: 'PostFooter: read more posts about given subjects',
+ })}
+ </dt>
<dd>
<ul className={styles.list}>{getTopics()}</ul>
</dd>
diff --git a/src/components/PostMeta/PostMeta.tsx b/src/components/PostMeta/PostMeta.tsx
index f95707a..86e4e71 100644
--- a/src/components/PostMeta/PostMeta.tsx
+++ b/src/components/PostMeta/PostMeta.tsx
@@ -1,9 +1,9 @@
import { config } from '@config/website';
-import { plural, t } from '@lingui/macro';
import { ArticleMeta } from '@ts/types/articles';
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';
@@ -26,6 +26,7 @@ const PostMeta = ({
website,
wordsCount,
} = meta;
+ const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : config.locales.defaultLocale;
const isThematic = () => router.asPath.includes('/thematique/');
@@ -62,24 +63,31 @@ const PostMeta = ({
};
const getCommentsCount = () => {
- switch (commentCount) {
- case 0:
- return t`No comments`;
- case 1:
- return t`1 comment`;
- default:
- return t`${commentCount} comments`;
- }
+ 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 t`less than 1 minute`;
- return plural(readingTime, {
- zero: '# minutes',
- one: '# minute',
- other: '# minutes',
- });
+ 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 getDates = () => {
@@ -91,14 +99,24 @@ const PostMeta = ({
return (
<>
<div className={styles.item}>
- <dt className={styles.term}>{t`Published on:`}</dt>
+ <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}>{t`Updated on:`}</dt>
+ <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>
@@ -114,14 +132,24 @@ const PostMeta = ({
<dl className={wrapperClass}>
{author && (
<div className={styles.item}>
- <dt className={styles.term}>{t`Written by:`}</dt>
+ <dt className={styles.term}>
+ {intl.formatMessage({
+ defaultMessage: 'Written by:',
+ description: 'Article meta',
+ })}
+ </dt>
<dd className={styles.description}>{author.name}</dd>
</div>
)}
{getDates()}
{readingTime !== undefined && wordsCount !== undefined && (
<div className={styles.item}>
- <dt className={styles.term}>{t`Reading time:`}</dt>
+ <dt className={styles.term}>
+ {intl.formatMessage({
+ defaultMessage: 'Reading time:',
+ description: 'Article meta',
+ })}
+ </dt>
<dd
className={styles.description}
title={`Approximately ${wordsCount.toLocaleString(locale)} words`}
@@ -132,20 +160,35 @@ const PostMeta = ({
)}
{results && (
<div className={styles.item}>
- <dt className={styles.term}>{t`Total: `}</dt>
- <dd className={styles.description}>
- {plural(results, {
- zero: '# articles',
- one: '# article',
- other: '# articles',
+ <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>
)}
{!isThematic() && thematics && thematics.length > 0 && (
<div className={styles.item}>
<dt className={styles.term}>
- {thematics.length > 1 ? t`Thematics:` : t`Thematic:`}
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}',
+ description: 'PostMeta: thematics list label',
+ },
+ { thematicsCount: thematics.length }
+ )}
</dt>
{getThematics()}
</div>
@@ -153,14 +196,26 @@ const PostMeta = ({
{isThematic() && topics && topics.length > 0 && (
<div className={styles.item}>
<dt className={styles.term}>
- {topics.length > 1 ? t`Topics:` : t`Topic:`}
+ {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}>{t`Website:`}</dt>
+ <dt className={styles.term}>
+ {intl.formatMessage({
+ defaultMessage: 'Website:',
+ description: 'PostMeta: website label',
+ })}
+ </dt>
<dd className={styles.description}>
<a href={website}>{website}</a>
</dd>
@@ -168,7 +223,12 @@ const PostMeta = ({
)}
{commentCount !== undefined && (
<div className={styles.item}>
- <dt className={styles.term}>{t`Comments:`}</dt>
+ <dt className={styles.term}>
+ {intl.formatMessage({
+ defaultMessage: 'Comments:',
+ description: 'PostMeta: comment count label',
+ })}
+ </dt>
<dd className={styles.description}>
{isArticle() ? (
<a href="#comments">{getCommentsCount()}</a>
diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx
index b084ca1..72ba638 100644
--- a/src/components/PostPreview/PostPreview.tsx
+++ b/src/components/PostPreview/PostPreview.tsx
@@ -1,15 +1,15 @@
-import PostMeta from '@components/PostMeta/PostMeta';
-import { t } from '@lingui/macro';
-import { ArticleMeta, ArticlePreview } from '@ts/types/articles';
-import Link from 'next/link';
-import styles from './PostPreview.module.scss';
-import Image from 'next/image';
import { ButtonLink } from '@components/Buttons';
import { ArrowIcon } from '@components/Icons';
+import PostMeta from '@components/PostMeta/PostMeta';
+import { config } from '@config/website';
import { TitleLevel } from '@ts/types/app';
-import { BlogPosting, WithContext } from 'schema-dts';
+import { ArticleMeta, ArticlePreview } from '@ts/types/articles';
+import Image from 'next/image';
import Head from 'next/head';
-import { config } from '@config/website';
+import Link from 'next/link';
+import { FormattedMessage } from 'react-intl';
+import { BlogPosting, WithContext } from 'schema-dts';
+import styles from './PostPreview.module.scss';
const PostPreview = ({
post,
@@ -97,11 +97,16 @@ const PostPreview = ({
></div>
<footer className={styles.footer}>
<ButtonLink target={`/article/${slug}`} position="left">
- {t`Read more`}
- <span className="screen-reader-text">
- {' '}
- {t({ message: `about ${title}`, comment: 'Post title' })}
- </span>
+ <FormattedMessage
+ defaultMessage="Read more<a11y> about {title}</a11y>"
+ description="PostPreview: read more link"
+ values={{
+ title,
+ a11y: (chunks: string) => (
+ <span className="screen-reader-text">{chunks}</span>
+ ),
+ }}
+ />
<ArrowIcon />
</ButtonLink>
</footer>
diff --git a/src/components/PostsList/PostsList.tsx b/src/components/PostsList/PostsList.tsx
index df9dfe4..16deee3 100644
--- a/src/components/PostsList/PostsList.tsx
+++ b/src/components/PostsList/PostsList.tsx
@@ -1,9 +1,9 @@
-import { t } from '@lingui/macro';
-import { PostsList as PostsListData } from '@ts/types/blog';
-import styles from './PostsList.module.scss';
import PostPreview from '@components/PostPreview/PostPreview';
-import { ForwardedRef, forwardRef, Fragment } from 'react';
+import { PostsList as PostsListData } from '@ts/types/blog';
import { sortPostsByYear } from '@utils/helpers/sort';
+import { ForwardedRef, forwardRef, Fragment } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './PostsList.module.scss';
const PostsList = (
{
@@ -15,6 +15,7 @@ const PostsList = (
},
ref: ForwardedRef<HTMLSpanElement>
) => {
+ const intl = useIntl();
const titleLevel = showYears ? 3 : 2;
const getPostsListByYear = () => {
@@ -32,7 +33,12 @@ const PostsList = (
<section key={year} className={styles.section}>
{showYears && (
<h2 className={styles.year}>
- <span className="screen-reader-text">{t`Published in`} </span>
+ <span className="screen-reader-text">
+ {intl.formatMessage({
+ defaultMessage: 'Published on',
+ description: 'PostsList: published on year label',
+ })}{' '}
+ </span>
{year}
</h2>
)}
@@ -62,7 +68,14 @@ const PostsList = (
};
if (page.posts.length === 0) {
- return <p key="no-result">{t`No results found.`}</p>;
+ return (
+ <p key="no-result">
+ {intl.formatMessage({
+ defaultMessage: 'No results found.',
+ description: 'PostsList: no results',
+ })}
+ </p>
+ );
} else {
return (
<Fragment key={page.pageInfo.endCursor}>
diff --git a/src/components/ProjectPreview/ProjectPreview.tsx b/src/components/ProjectPreview/ProjectPreview.tsx
index cba0b02..043d945 100644
--- a/src/components/ProjectPreview/ProjectPreview.tsx
+++ b/src/components/ProjectPreview/ProjectPreview.tsx
@@ -1,12 +1,13 @@
-import { t } from '@lingui/macro';
import { Project } from '@ts/types/app';
import { slugify } from '@utils/helpers/slugify';
import Image from 'next/image';
import Link from 'next/link';
+import { useIntl } from 'react-intl';
import styles from './ProjectPreview.module.scss';
const ProjectPreview = ({ project }: { project: Project }) => {
const { id, meta, tagline, title } = project;
+ const intl = useIntl();
return (
<Link href={`/projet/${project.slug}`}>
@@ -20,7 +21,13 @@ const ProjectPreview = ({ project }: { project: Project }) => {
layout="fill"
objectFit="contain"
objectPosition="center"
- alt={t`${title} picture`}
+ alt={intl.formatMessage(
+ {
+ defaultMessage: '{title} picture',
+ description: 'ProjectPreview: cover alt text',
+ },
+ { title }
+ )}
/>
</div>
)}
@@ -36,7 +43,16 @@ const ProjectPreview = ({ project }: { project: Project }) => {
<dl className={styles.meta}>
{meta.technologies && (
<div className={styles.meta__item}>
- <dt className="screen-reader-text">{t`Technologies:`}</dt>
+ <dt className="screen-reader-text">
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}',
+ description: 'ProjectPreview: technologies list label',
+ },
+ { count: meta.technologies.length }
+ )}
+ </dt>
{meta.technologies.map((techno) => (
<dd key={slugify(techno)} className={styles.techno}>
{techno}
diff --git a/src/components/ProjectSummary/ProjectSummary.tsx b/src/components/ProjectSummary/ProjectSummary.tsx
index b32c11f..f2d73b6 100644
--- a/src/components/ProjectSummary/ProjectSummary.tsx
+++ b/src/components/ProjectSummary/ProjectSummary.tsx
@@ -1,13 +1,14 @@
import GithubIcon from '@assets/images/social-media/github.svg';
import GitlabIcon from '@assets/images/social-media/gitlab.svg';
import { config } from '@config/website';
-import { t } from '@lingui/macro';
import { ProjectMeta } from '@ts/types/app';
import { getFormattedDate } from '@utils/helpers/format';
import { slugify } from '@utils/helpers/slugify';
import useGithubApi from '@utils/hooks/useGithubApi';
+import IntlMessageFormat from 'intl-messageformat';
import Image from 'next/image';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import styles from './ProjectSummary.module.scss';
const ProjectSummary = ({
@@ -20,6 +21,7 @@ const ProjectSummary = ({
meta: ProjectMeta;
}) => {
const { hasCover, license, repos, technologies } = meta;
+ const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : config.locales.defaultLocale;
const { data } = useGithubApi(repos?.github ? repos.github : '');
@@ -30,7 +32,10 @@ const ProjectSummary = ({
<div className={styles.cover}>
<Image
src={`/projects/${id}.jpg`}
- alt={t`${title} preview`}
+ alt={intl.formatMessage({
+ defaultMessage: '{title} preview',
+ description: 'ProjectSummary: cover alt text',
+ })}
layout="fill"
objectFit="contain"
/>
@@ -39,7 +44,12 @@ const ProjectSummary = ({
<dl className={styles.info}>
{data && (
<div className={styles.info__item}>
- <dt>{t`Created on`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Created on:',
+ description: 'ProjectSummary: creation date label',
+ })}
+ </dt>
<dd>
<time dateTime={data.created_at}>
{getFormattedDate(data.created_at, locale)}
@@ -49,7 +59,12 @@ const ProjectSummary = ({
)}
{data && (
<div className={styles.info__item}>
- <dt>{t`Last updated on`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Last updated on:',
+ description: 'ProjectSummary: update date label',
+ })}
+ </dt>
<dd>
<time dateTime={data.updated_at}>
{getFormattedDate(data.updated_at, locale)}
@@ -58,12 +73,26 @@ const ProjectSummary = ({
</div>
)}
<div className={styles.info__item}>
- <dt>{t`License`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'License:',
+ description: 'ProjectSummary: license label',
+ })}
+ </dt>
<dd>{license}</dd>
</div>
{technologies && (
<div className={styles.info__item}>
- <dt>{t`Technologies`}</dt>
+ <dt>
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}',
+ description: 'ProjectSummary: technologies list label',
+ },
+ { count: technologies.length }
+ )}
+ </dt>
{technologies.map((techno) => (
<dd
key={slugify(techno)}
@@ -76,7 +105,16 @@ const ProjectSummary = ({
)}
{repos && (
<div className={styles.info__item}>
- <dt>{t`Repositories`}</dt>
+ <dt>
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{count, plural, =0 {Repositories:} one {Repository:} other {Repositories:}}',
+ description: 'ProjectSummary: repositories list label',
+ },
+ { count: Object.keys(repos).length }
+ )}
+ </dt>
{repos.github && (
<dd className={styles['inline-data']}>
<a
@@ -103,12 +141,24 @@ const ProjectSummary = ({
)}
{data && repos && (
<div>
- <dt>{t`Popularity`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Popularity:',
+ description: 'ProjectSummary: popularity label',
+ })}
+ </dt>
{repos.github && (
<dd>
⭐&nbsp;
<a href={`https://github.com/${repos.github}/stargazers`}>
- {t`${data.stargazers_count} stars on Github`}
+ {intl.formatMessage(
+ {
+ defaultMessage:
+ '{starsCount, plural, =0 {No stars on Github} one {# star on Github} other {# stars on Github}}',
+ description: 'ProjectPreview: technologies list label',
+ },
+ { starsCount: data.stargazers_count }
+ )}
</a>
</dd>
)}
diff --git a/src/components/SearchForm/SearchForm.tsx b/src/components/SearchForm/SearchForm.tsx
index cefda85..38ae60d 100644
--- a/src/components/SearchForm/SearchForm.tsx
+++ b/src/components/SearchForm/SearchForm.tsx
@@ -1,12 +1,13 @@
import { ButtonSubmit } from '@components/Buttons';
import { Form, Input } from '@components/Form';
import { SearchIcon } from '@components/Icons';
-import { t } from '@lingui/macro';
import { useRouter } from 'next/router';
import { FormEvent, useEffect, useRef, useState } from 'react';
+import { useIntl } from 'react-intl';
import styles from './SearchForm.module.scss';
const SearchForm = ({ isOpened }: { isOpened: boolean }) => {
+ const intl = useIntl();
const [query, setQuery] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
@@ -27,7 +28,12 @@ const SearchForm = ({ isOpened }: { isOpened: boolean }) => {
return (
<>
- <div className={styles.title}>{t`Search`}</div>
+ <div className={styles.title}>
+ {intl.formatMessage({
+ defaultMessage: 'Search',
+ description: 'SearchForm : form title',
+ })}
+ </div>
<Form submitHandler={launchSearch} modifier="search">
<Input
ref={inputRef}
@@ -39,7 +45,12 @@ const SearchForm = ({ isOpened }: { isOpened: boolean }) => {
/>
<ButtonSubmit modifier="search">
<SearchIcon />
- <span className="screen-reader-text">{t`Search`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage({
+ defaultMessage: 'Search',
+ description: 'SearchForm: search button text',
+ })}
+ </span>
</ButtonSubmit>
</Form>
</>
diff --git a/src/components/Settings/ReduceMotion/ReduceMotion.tsx b/src/components/Settings/ReduceMotion/ReduceMotion.tsx
index 01f8b67..c7b5775 100644
--- a/src/components/Settings/ReduceMotion/ReduceMotion.tsx
+++ b/src/components/Settings/ReduceMotion/ReduceMotion.tsx
@@ -1,9 +1,10 @@
import { Toggle } from '@components/Form';
-import { t } from '@lingui/macro';
import { LocalStorage } from '@services/local-storage';
import { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
const ReduceMotion = () => {
+ const intl = useIntl();
const [isDeactivated, setIsDeactivated] = useState<boolean>(false);
useEffect(() => {
@@ -23,9 +24,18 @@ const ReduceMotion = () => {
return (
<Toggle
id="reduced-motion"
- label={t`Animations:`}
- leftChoice={t`On`}
- rightChoice={t`Off`}
+ label={intl.formatMessage({
+ defaultMessage: 'Animations:',
+ description: 'ReduceMotion: toggle label',
+ })}
+ leftChoice={intl.formatMessage({
+ defaultMessage: 'On',
+ description: 'ReduceMotion: toggle on label',
+ })}
+ rightChoice={intl.formatMessage({
+ defaultMessage: 'Off',
+ description: 'ReduceMotion: toggle off label',
+ })}
value={isDeactivated}
changeHandler={updateState}
/>
diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx
index bd2f33d..80eb0c3 100644
--- a/src/components/Settings/Settings.tsx
+++ b/src/components/Settings/Settings.tsx
@@ -1,14 +1,20 @@
import { CogIcon } from '@components/Icons';
import ThemeToggle from '@components/Settings/ThemeToggle/ThemeToggle';
-import { t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
import ReduceMotion from './ReduceMotion/ReduceMotion';
import styles from './Settings.module.scss';
const Settings = () => {
+ const intl = useIntl();
+
return (
<>
<div className={styles.title}>
- <CogIcon /> {t`Settings`}
+ <CogIcon />{' '}
+ {intl.formatMessage({
+ defaultMessage: 'Settings',
+ description: 'Settings: modal title',
+ })}
</div>
<ThemeToggle />
<ReduceMotion />
diff --git a/src/components/Settings/ThemeToggle/ThemeToggle.tsx b/src/components/Settings/ThemeToggle/ThemeToggle.tsx
index e14f39a..5b7a34d 100644
--- a/src/components/Settings/ThemeToggle/ThemeToggle.tsx
+++ b/src/components/Settings/ThemeToggle/ThemeToggle.tsx
@@ -1,11 +1,12 @@
import { Toggle } from '@components/Form';
import { MoonIcon, SunIcon } from '@components/Icons';
import Spinner from '@components/Spinner/Spinner';
-import { t } from '@lingui/macro';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
const ThemeToggle = () => {
+ const intl = useIntl();
const [isMounted, setIsMounted] = useState<boolean>(false);
const { resolvedTheme, setTheme } = useTheme();
@@ -24,7 +25,10 @@ const ThemeToggle = () => {
return (
<Toggle
id="dark-theme"
- label={t`Theme:`}
+ label={intl.formatMessage({
+ defaultMessage: 'Theme:',
+ description: 'ThemeToggle: toggle label',
+ })}
leftChoice={<SunIcon />}
rightChoice={<MoonIcon />}
value={isDarkTheme}
diff --git a/src/components/Spinner/Spinner.tsx b/src/components/Spinner/Spinner.tsx
index cfa5717..381fbb6 100644
--- a/src/components/Spinner/Spinner.tsx
+++ b/src/components/Spinner/Spinner.tsx
@@ -1,13 +1,20 @@
-import { t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
import styles from './Spinner.module.scss';
const Spinner = () => {
+ const intl = useIntl();
+
return (
<div className={styles.wrapper}>
<div className={styles.ball}></div>
<div className={styles.ball}></div>
<div className={styles.ball}></div>
- <div className={styles.text}>{t`Loading...`}</div>
+ <div className={styles.text}>
+ {intl.formatMessage({
+ defaultMessage: 'Loading...',
+ description: 'Spinner: loading text',
+ })}
+ </div>
</div>
);
};
diff --git a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx
index 52b5c06..6a19d92 100644
--- a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx
+++ b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx
@@ -1,6 +1,6 @@
-import { t } from '@lingui/macro';
import { TitleLevel } from '@ts/types/app';
import { ReactNode, useState } from 'react';
+import { useIntl } from 'react-intl';
import styles from './ExpandableWidget.module.scss';
const ExpandableWidget = ({
@@ -16,6 +16,7 @@ const ExpandableWidget = ({
expand?: boolean;
withBorders?: boolean;
}) => {
+ const intl = useIntl();
const [isExpanded, setIsExpanded] = useState<boolean>(expand);
const handleExpanse = () => setIsExpanded((prev) => !prev);
@@ -34,7 +35,15 @@ const ExpandableWidget = ({
<div className={wrapperClasses}>
<button type="button" className={styles.header} onClick={handleExpanse}>
<span className="screen-reader-text">
- {isExpanded ? t`Collapse` : t`Expand`}
+ {isExpanded
+ ? intl.formatMessage({
+ defaultMessage: 'Collapse',
+ description: 'ExpandableWidget: collapse text',
+ })
+ : intl.formatMessage({
+ defaultMessage: 'Expand',
+ description: 'ExpandableWidget: expand text',
+ })}
</span>
<TitleTag className={styles.title}>{title}</TitleTag>
<span className={styles.icon} aria-hidden={true}></span>
diff --git a/src/components/Widgets/CVPreview/CVPreview.tsx b/src/components/Widgets/CVPreview/CVPreview.tsx
index e52a9b2..08a4c72 100644
--- a/src/components/Widgets/CVPreview/CVPreview.tsx
+++ b/src/components/Widgets/CVPreview/CVPreview.tsx
@@ -1,7 +1,7 @@
import { ExpandableWidget } from '@components/WidgetParts';
-import { Trans } from '@lingui/macro';
import Image from 'next/image';
import Link from 'next/link';
+import { FormattedMessage } from 'react-intl';
import styles from './CVPreview.module.scss';
const CVPreview = ({
@@ -25,9 +25,17 @@ const CVPreview = ({
/>
</div>
<p>
- <Trans>
- Download <Link href={pdf}>CV in PDF</Link>
- </Trans>
+ <FormattedMessage
+ defaultMessage="Download <link>CV in PDF</link>"
+ description="CVPreview: download as PDF link"
+ values={{
+ link: (chunks: string) => (
+ <Link href={pdf}>
+ <a>{chunks}</a>
+ </Link>
+ ),
+ }}
+ />
</p>
</ExpandableWidget>
);
diff --git a/src/components/Widgets/RecentPosts/RecentPosts.tsx b/src/components/Widgets/RecentPosts/RecentPosts.tsx
index 8022bff..08ce7e4 100644
--- a/src/components/Widgets/RecentPosts/RecentPosts.tsx
+++ b/src/components/Widgets/RecentPosts/RecentPosts.tsx
@@ -1,16 +1,17 @@
import Spinner from '@components/Spinner/Spinner';
import { config } from '@config/website';
-import { t } from '@lingui/macro';
import { getPublishedPosts } from '@services/graphql/queries';
import { ArticlePreview } from '@ts/types/articles';
import { getFormattedDate } from '@utils/helpers/format';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import useSWR from 'swr';
import styles from './RecentPosts.module.scss';
const RecentPosts = () => {
+ const intl = useIntl();
const { data, error } = useSWR('/recent-posts', () =>
getPublishedPosts({ first: 3 })
);
@@ -36,7 +37,12 @@ const RecentPosts = () => {
)}
<h3 className={styles.title}>{post.title}</h3>
<dl className={styles.meta}>
- <dt>{t`Published on:`}</dt>
+ <dt>
+ {intl.formatMessage({
+ defaultMessage: 'Published on:',
+ description: 'RecentPosts: publication date label',
+ })}
+ </dt>
<dd>
<time dateTime={post.dates.publication}>
{getFormattedDate(post.dates.publication, locale)}
@@ -51,7 +57,11 @@ const RecentPosts = () => {
};
const getPostsItems = () => {
- if (error) return t`Failed to load.`;
+ if (error)
+ return intl.formatMessage({
+ defaultMessage: 'Failed to load.',
+ description: 'RecentPosts: failed to load text',
+ });
if (!data) return <Spinner />;
return data.posts.map((post) => getPost(post));
diff --git a/src/components/Widgets/RelatedThematics/RelatedThematics.tsx b/src/components/Widgets/RelatedThematics/RelatedThematics.tsx
index afe3460..c6be3ca 100644
--- a/src/components/Widgets/RelatedThematics/RelatedThematics.tsx
+++ b/src/components/Widgets/RelatedThematics/RelatedThematics.tsx
@@ -1,9 +1,10 @@
import { ExpandableWidget, List } from '@components/WidgetParts';
-import { t } from '@lingui/macro';
import { ThematicPreview } from '@ts/types/taxonomies';
import Link from 'next/link';
+import { useIntl } from 'react-intl';
const RelatedThematics = ({ thematics }: { thematics: ThematicPreview[] }) => {
+ const intl = useIntl();
const sortedThematics = [...thematics].sort((a, b) =>
a.title.localeCompare(b.title)
);
@@ -20,7 +21,14 @@ const RelatedThematics = ({ thematics }: { thematics: ThematicPreview[] }) => {
return (
<ExpandableWidget
- title={thematics.length > 1 ? t`Related thematics` : t`Related thematic`}
+ title={intl.formatMessage(
+ {
+ defaultMessage:
+ '{thematicsCount, plural, =0 {Related thematics} one {Related thematic} other {Related thematics}}',
+ description: 'RelatedThematics: widget title',
+ },
+ { thematicsCount: thematics.length }
+ )}
withBorders={true}
>
<List items={thematicsList} />
diff --git a/src/components/Widgets/RelatedTopics/RelatedTopics.tsx b/src/components/Widgets/RelatedTopics/RelatedTopics.tsx
index 178e5bc..b9699e2 100644
--- a/src/components/Widgets/RelatedTopics/RelatedTopics.tsx
+++ b/src/components/Widgets/RelatedTopics/RelatedTopics.tsx
@@ -1,9 +1,10 @@
import { ExpandableWidget, List } from '@components/WidgetParts';
-import { t } from '@lingui/macro';
import { TopicPreview } from '@ts/types/taxonomies';
import Link from 'next/link';
+import { useIntl } from 'react-intl';
const RelatedTopics = ({ topics }: { topics: TopicPreview[] }) => {
+ const intl = useIntl();
const sortedTopics = [...topics].sort((a, b) =>
a.title.localeCompare(b.title)
);
@@ -20,7 +21,14 @@ const RelatedTopics = ({ topics }: { topics: TopicPreview[] }) => {
return (
<ExpandableWidget
- title={topicsList.length > 1 ? t`Related topics` : t`Related topic`}
+ title={intl.formatMessage(
+ {
+ defaultMessage:
+ '{topicsCount, plural, =0 {Related topics} one {Related topic} other {Related topics}}',
+ description: 'RelatedTopics: widget title',
+ },
+ { topicsCount: topicsList.length }
+ )}
withBorders={true}
>
<List items={topicsList} />
diff --git a/src/components/Widgets/Sharing/Sharing.tsx b/src/components/Widgets/Sharing/Sharing.tsx
index 89b48ca..1025717 100644
--- a/src/components/Widgets/Sharing/Sharing.tsx
+++ b/src/components/Widgets/Sharing/Sharing.tsx
@@ -1,8 +1,8 @@
import { ExpandableWidget } from '@components/WidgetParts';
import sharingMedia from '@config/sharing';
-import { t } from '@lingui/macro';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
import styles from './Sharing.module.scss';
type Parameters = {
@@ -20,6 +20,7 @@ type Website = {
};
const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => {
+ const intl = useIntl();
const [pageExcerpt, setPageExcerpt] = useState('');
const [pageUrl, setPageUrl] = useState('');
const [domainName, setDomainName] = useState('');
@@ -54,8 +55,14 @@ const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => {
switch (key) {
case 'content':
if (id === 'email') {
- const intro = t`Introduction:`;
- const readMore = t`Read more here:`;
+ const intro = intl.formatMessage({
+ defaultMessage: 'Introduction:',
+ description: 'Sharing: email content prefix',
+ });
+ const readMore = intl.formatMessage({
+ defaultMessage: 'Read more here:',
+ description: 'Sharing: content link prefix',
+ });
const body = `${intro}\n\n"${pageExcerpt}"\n\n${readMore} ${pageUrl}`;
sharingUrl += encodeURI(body);
} else {
@@ -63,7 +70,16 @@ const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => {
}
break;
case 'title':
- const prefix = id === 'email' ? t`Seen on ${domainName}:` : '';
+ const prefix =
+ id === 'email'
+ ? intl.formatMessage(
+ {
+ defaultMessage: 'Seen on {domainName}:',
+ description: 'Sharing: seen on text',
+ },
+ { domainName }
+ )
+ : '';
sharingUrl += encodeURI(`${prefix} ${title}`);
break;
case 'url':
@@ -94,7 +110,15 @@ const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => {
title={name}
className={`${styles.link} ${styles[linkModifier]}`}
>
- <span className="screen-reader-text">{t`Share on ${name}`}</span>
+ <span className="screen-reader-text">
+ {intl.formatMessage(
+ {
+ defaultMessage: 'Share on {name}',
+ description: 'Sharing: share on social network text',
+ },
+ { name }
+ )}
+ </span>
</a>
</li>
);
@@ -102,7 +126,13 @@ const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => {
};
return (
- <ExpandableWidget title={t`Share`} expand={true}>
+ <ExpandableWidget
+ title={intl.formatMessage({
+ defaultMessage: 'Share',
+ description: 'Sharing: widget title',
+ })}
+ expand={true}
+ >
<ul className={`${styles.list} ${styles['list--sharing']}`}>
{getItems()}
</ul>
diff --git a/src/components/Widgets/ThematicsList/ThematicsList.tsx b/src/components/Widgets/ThematicsList/ThematicsList.tsx
index e5162b4..9b1f03a 100644
--- a/src/components/Widgets/ThematicsList/ThematicsList.tsx
+++ b/src/components/Widgets/ThematicsList/ThematicsList.tsx
@@ -1,10 +1,10 @@
import Spinner from '@components/Spinner/Spinner';
import { ExpandableWidget, List } from '@components/WidgetParts';
-import { t } from '@lingui/macro';
import { getAllThematics } from '@services/graphql/queries';
import { TitleLevel } from '@ts/types/app';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import useSWR from 'swr';
const ThematicsList = ({
@@ -14,6 +14,7 @@ const ThematicsList = ({
title: string;
titleLevel?: TitleLevel;
}) => {
+ const intl = useIntl();
const router = useRouter();
const isThematic = () => router.asPath.includes('/thematique/');
const currentThematicSlug = isThematic()
@@ -23,7 +24,15 @@ const ThematicsList = ({
const { data, error } = useSWR('/api/thematics', getAllThematics);
const getList = () => {
- if (error) return <ul>{t`Failed to load.`}</ul>;
+ if (error)
+ return (
+ <ul>
+ {intl.formatMessage({
+ defaultMessage: 'Failed to load.',
+ description: 'ThematicsList: failed to load text',
+ })}
+ </ul>
+ );
if (!data)
return (
<ul>
diff --git a/src/components/Widgets/ToC/ToC.tsx b/src/components/Widgets/ToC/ToC.tsx
index 6010354..c499478 100644
--- a/src/components/Widgets/ToC/ToC.tsx
+++ b/src/components/Widgets/ToC/ToC.tsx
@@ -1,11 +1,15 @@
import { ExpandableWidget, OrderedList } from '@components/WidgetParts';
-import { t } from '@lingui/macro';
import { Heading } from '@ts/types/app';
import useHeadingsTree from '@utils/hooks/useHeadingsTree';
+import { useIntl } from 'react-intl';
const ToC = () => {
+ const intl = useIntl();
const headingsTree = useHeadingsTree('article');
- const title = t`Table of contents`;
+ const title = intl.formatMessage({
+ defaultMessage: 'Table of contents',
+ description: 'ToC: widget title',
+ });
const getItems = (headings: Heading[]) => {
return headings.map((heading) => {
diff --git a/src/components/Widgets/TopicsList/TopicsList.tsx b/src/components/Widgets/TopicsList/TopicsList.tsx
index 5b0c44e..80341c3 100644
--- a/src/components/Widgets/TopicsList/TopicsList.tsx
+++ b/src/components/Widgets/TopicsList/TopicsList.tsx
@@ -1,10 +1,10 @@
import Spinner from '@components/Spinner/Spinner';
import { ExpandableWidget, List } from '@components/WidgetParts';
-import { t } from '@lingui/macro';
import { getAllTopics } from '@services/graphql/queries';
import { TitleLevel } from '@ts/types/app';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import useSWR from 'swr';
const TopicsList = ({
@@ -14,6 +14,7 @@ const TopicsList = ({
title: string;
titleLevel?: TitleLevel;
}) => {
+ const intl = useIntl();
const router = useRouter();
const isTopic = () => router.asPath.includes('/sujet/');
const currentTopicSlug = isTopic()
@@ -23,7 +24,15 @@ const TopicsList = ({
const { data, error } = useSWR('/api/topics', getAllTopics);
const getList = () => {
- if (error) return <ul>{t`Failed to load.`}</ul>;
+ if (error)
+ return (
+ <ul>
+ {intl.formatMessage({
+ defaultMessage: 'Failed to load.',
+ description: 'TopicsList: failed to load text',
+ })}
+ </ul>
+ );
if (!data)
return (
<ul>