diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-01-29 18:21:37 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-01-29 19:02:57 +0100 |
| commit | e4d5b8151802517b2943756fc0d09ffa95e2c4e2 (patch) | |
| tree | 9e99137a7b64ea7993a8311a7162336a551be8b2 /src/components | |
| parent | 47b854de26dea24e7838fd0804df103dee99635f (diff) | |
chore: replace lingui functions with react-intl
Diffstat (limited to 'src/components')
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> ⭐ <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> |
