aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.babelrc1
-rw-r--r--.eslintrc.json6
-rw-r--r--.gitignore3
-rw-r--r--next.config.js7
-rw-r--r--package.json11
-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
-rw-r--r--src/config/nav.ts14
-rw-r--r--src/config/seo.ts33
-rw-r--r--src/config/sharing.ts4
-rw-r--r--src/config/website.ts5
-rw-r--r--src/i18n/en.json586
-rw-r--r--src/i18n/fr.json586
-rw-r--r--src/pages/404.tsx66
-rw-r--r--src/pages/_app.tsx26
-rw-r--r--src/pages/article/[slug].tsx17
-rw-r--r--src/pages/blog/index.tsx106
-rw-r--r--src/pages/contact.tsx109
-rw-r--r--src/pages/cv.tsx75
-rw-r--r--src/pages/index.tsx90
-rw-r--r--src/pages/mentions-legales.tsx57
-rw-r--r--src/pages/projet/[slug].tsx4
-rw-r--r--src/pages/projets.tsx34
-rw-r--r--src/pages/recherche/index.tsx80
-rw-r--r--src/pages/sujet/[slug].tsx42
-rw-r--r--src/pages/thematique/[slug].tsx46
-rw-r--r--src/utils/helpers/i18n.ts91
-rw-r--r--src/utils/helpers/prism.ts22
-rw-r--r--yarn.lock640
62 files changed, 3077 insertions, 529 deletions
diff --git a/.babelrc b/.babelrc
index 4cdf3d2..28dcb52 100644
--- a/.babelrc
+++ b/.babelrc
@@ -2,6 +2,7 @@
"presets": ["next/babel"],
"plugins": [
"macros",
+ ["formatjs", { "ast": true }],
[
"prismjs",
{
diff --git a/.eslintrc.json b/.eslintrc.json
index 4d765f2..46d209f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,7 @@
{
- "extends": ["next/core-web-vitals", "prettier"]
+ "extends": ["next/core-web-vitals", "prettier"],
+ "plugins": ["formatjs"],
+ "rules": {
+ "formatjs/enforce-default-message": ["error", "literal"]
+ }
}
diff --git a/.gitignore b/.gitignore
index d4504c5..182fe2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@ yarn-error.log*
# dotenv
.env*
!.env.example
+
+# i18n
+lang
diff --git a/next.config.js b/next.config.js
index a26eda6..3583639 100644
--- a/next.config.js
+++ b/next.config.js
@@ -30,7 +30,12 @@ const nextConfig = {
path.join(__dirname, 'node_modules'),
],
},
- webpack: (config) => {
+ webpack: (config, { dev }) => {
+ if (!dev) {
+ // https://formatjs.io/docs/guides/advanced-usage#react-intl-without-parser-40-smaller
+ config.resolve.alias['@formatjs/icu-messageformat-parser'] =
+ '@formatjs/icu-messageformat-parser/no-parser';
+ }
config.module.rules.push(
{
test: /\.pdf/,
diff --git a/package.json b/package.json
index 3ce04a9..f821652 100644
--- a/package.json
+++ b/package.json
@@ -18,12 +18,14 @@
},
"private": true,
"scripts": {
+ "predev": "npm run i18n:compile",
"dev": "next dev",
+ "prebuild": "npm run i18n:compile",
"build": "next build",
"start": "next start",
"lint": "next lint",
- "i18n:compile": "lingui compile",
- "i18n:extract": "NODE_ENV=development lingui extract",
+ "i18n:compile": "formatjs compile-folder src/i18n lang/",
+ "i18n:extract": "formatjs extract 'src/**/*.ts*' --out-file src/i18n/en.json",
"release": "standard-version -s",
"test": "jest",
"test:coverage": "jest --coverage",
@@ -36,10 +38,12 @@
"@next/mdx": "^12.0.7",
"@types/mdx": "^2.0.1",
"@types/prismjs": "^1.16.6",
+ "babel-plugin-formatjs": "^10.3.17",
"babel-plugin-prismjs": "^2.1.0",
"feed": "^4.2.2",
"graphql": "^16.1.0",
"graphql-request": "^3.7.0",
+ "intl-messageformat": "^9.11.3",
"modern-normalize": "^1.1.0",
"next": "12.0.7",
"next-themes": "^0.0.15",
@@ -47,6 +51,7 @@
"prismjs": "^1.25.0",
"react": "17.0.2",
"react-dom": "17.0.2",
+ "react-intl": "^5.24.4",
"schema-dts": "^1.0.0",
"swr": "^1.1.1"
},
@@ -56,6 +61,7 @@
"@babel/preset-react": "^7.16.0",
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
+ "@formatjs/cli": "^4.8.1",
"@lingui/cli": "^3.13.0",
"@lingui/loader": "^3.13.0",
"@lingui/macro": "^3.13.0",
@@ -68,6 +74,7 @@
"eslint": "^8.4.1",
"eslint-config-next": "^12.0.7",
"eslint-config-prettier": "^8.3.0",
+ "eslint-plugin-formatjs": "^2.20.5",
"husky": "^7.0.4",
"jest": "^27.4.4",
"lint-staged": "^12.1.2",
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>
diff --git a/src/config/nav.ts b/src/config/nav.ts
deleted file mode 100644
index ff69a78..0000000
--- a/src/config/nav.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { t } from '@lingui/macro';
-import { NavItem } from '@ts/types/nav';
-
-export const mainNav: NavItem[] = [
- { id: 'home', name: t`Home`, slug: '/' },
- { id: 'blog', name: t`Blog`, slug: '/blog' },
- { id: 'projects', name: t`Projects`, slug: '/projets' },
- { id: 'cv', name: t`Resume`, slug: '/cv' },
- { id: 'contact', name: t`Contact`, slug: '/contact' },
-];
-
-export const footerNav: NavItem[] = [
- { id: 'legal-notice', name: t`Legal notice`, slug: '/mentions-legales' },
-];
diff --git a/src/config/seo.ts b/src/config/seo.ts
deleted file mode 100644
index 3ff08b6..0000000
--- a/src/config/seo.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { t } from '@lingui/macro';
-import { config } from './website';
-
-export const seo = {
- homepage: {
- title: t`${config.name} | Front-end developer: WordPress/React`,
- description: t`${config.name} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.`,
- },
- blog: {
- title: t`Blog: development, open source - ${config.name}`,
- description: t`Discover ${config.name}'s writings. He talks about web development, Linux and open source mostly.`,
- },
- cv: {
- title: t`CV Front-end developer - ${config.name}`,
- description: t`Discover the curriculum of ${config.name}, front-end developer located in France: skills, experiences and training.`,
- },
- contact: {
- title: t`Contact form - ${config.name}`,
- description: t`Contact ${config.name} through its website. All you need to do it's to fill the contact form.`,
- },
- legalNotice: {
- title: t`Legal notice - ${config.name}`,
- description: t`Discover the legal notice of ${config.name}'s website.`,
- },
- error404: {
- title: t`Error 404: Page not found - ${config.name}`,
- description: '',
- },
- projects: {
- title: t`Projects: open-source makings - ${config.name}`,
- description: t`Discover ${config.name} projects. Mostly related to web development and open source.`,
- },
-};
diff --git a/src/config/sharing.ts b/src/config/sharing.ts
index 580145e..9e84801 100644
--- a/src/config/sharing.ts
+++ b/src/config/sharing.ts
@@ -1,5 +1,3 @@
-import { t } from '@lingui/macro';
-
const sharingMedia = [
{
id: 'diaspora',
@@ -58,7 +56,7 @@ const sharingMedia = [
},
{
id: 'email',
- name: t`Email`,
+ name: 'Email',
parameters: {
content: 'body',
image: '',
diff --git a/src/config/website.ts b/src/config/website.ts
index 1b0e2d3..81c493f 100644
--- a/src/config/website.ts
+++ b/src/config/website.ts
@@ -1,8 +1,6 @@
-import { t } from '@lingui/macro';
-
export const config = {
name: 'Armand Philippot',
- baseline: t`Front-end developer`,
+ baseline: 'Front-end developer',
copyright: {
startYear: '2012',
endYear: new Date().getFullYear(),
@@ -10,6 +8,7 @@ export const config = {
locales: {
defaultLocale: 'fr',
defaultCountry: 'FR',
+ supported: ['en', 'fr'],
},
postsPerPage: 10,
twitterId: '@ArmandPhilippot',
diff --git a/src/i18n/en.json b/src/i18n/en.json
new file mode 100644
index 0000000..23f5278
--- /dev/null
+++ b/src/i18n/en.json
@@ -0,0 +1,586 @@
+{
+ "+4tiVb": {
+ "defaultMessage": "Others topics",
+ "description": "TopicPage: topics list widget title"
+ },
+ "+COyEW": {
+ "defaultMessage": "{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}",
+ "description": "PostMeta: topics list label"
+ },
+ "+Dre5J": {
+ "defaultMessage": "Open-source projects",
+ "description": "CVPage: social media widget title"
+ },
+ "+Y+tLK": {
+ "defaultMessage": "Blog: development, open source - {websiteName}",
+ "description": "BlogPage: SEO - Page title"
+ },
+ "/IirIt": {
+ "defaultMessage": "Legal notice",
+ "description": "LegalNoticePage: page title"
+ },
+ "/ly3AC": {
+ "defaultMessage": "Copy",
+ "description": "Prism: copy button text (no clicked)"
+ },
+ "00Pf5p": {
+ "defaultMessage": "Failed to load.",
+ "description": "TopicsList: failed to load text"
+ },
+ "16zl9Z": {
+ "defaultMessage": "You are here:",
+ "description": "Breadcrumb: You are here prefix"
+ },
+ "18h/t0": {
+ "defaultMessage": "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.",
+ "description": "BlogPage: SEO - Meta description"
+ },
+ "1h+N2z": {
+ "defaultMessage": "Published on:",
+ "description": "RecentPosts: publication date label"
+ },
+ "2D9tB5": {
+ "defaultMessage": "Topics",
+ "description": "BlogPage: topics list widget title"
+ },
+ "2pykor": {
+ "defaultMessage": "{title} picture",
+ "description": "ProjectPreview: cover alt text"
+ },
+ "310o3F": {
+ "defaultMessage": "Error 404: Page not found - {websiteName}",
+ "description": "404Page: SEO - Page title"
+ },
+ "43OmTY": {
+ "defaultMessage": "Thanks. Your message was successfully sent. I will answer it as soon as possible.",
+ "description": "ContactPage: success message"
+ },
+ "48Ww//": {
+ "defaultMessage": "Page not found.",
+ "description": "404Page: SEO - Meta description"
+ },
+ "4zAUSu": {
+ "defaultMessage": "Legal notice - {websiteName}",
+ "description": "LegalNoticePage: SEO - Page title"
+ },
+ "7TbbIk": {
+ "defaultMessage": "Blog",
+ "description": "BlogPage: page title"
+ },
+ "8Ls2mD": {
+ "defaultMessage": "Please fill the form to contact me.",
+ "description": "ContactPage: page introduction"
+ },
+ "A4LTGq": {
+ "defaultMessage": "Discover search results for {query}",
+ "description": "SearchPage: meta description with query"
+ },
+ "AN9iy7": {
+ "defaultMessage": "Contact",
+ "description": "ContactPage: page title"
+ },
+ "AnaPbu": {
+ "defaultMessage": "Search",
+ "description": "SearchForm: search button text"
+ },
+ "B9OCyV": {
+ "defaultMessage": "Others formats",
+ "description": "CVPage: cv preview widget title"
+ },
+ "C/XGkH": {
+ "defaultMessage": "Failed to load.",
+ "description": "BlogPage: failed to load text"
+ },
+ "CSimmh": {
+ "defaultMessage": "{articlesCount, plural, =0 {# articles} one {# article} other {# articles}} out of a total of {total}",
+ "description": "PaginationCursor: loaded articles count message"
+ },
+ "CT3ydM": {
+ "defaultMessage": "{date} at {time}",
+ "description": "Comment: publication date"
+ },
+ "CWi0go": {
+ "defaultMessage": "Created on:",
+ "description": "ProjectSummary: creation date label"
+ },
+ "CzTbM4": {
+ "defaultMessage": "Contact",
+ "description": "ContactPage: breadcrumb item"
+ },
+ "Dq6+WH": {
+ "defaultMessage": "Thematics",
+ "description": "SearchPage: thematics list widget title"
+ },
+ "Enij19": {
+ "defaultMessage": "Home",
+ "description": "Breadcrumb: Home item"
+ },
+ "EvODgw": {
+ "defaultMessage": "Published on",
+ "description": "PostsList: published on year label"
+ },
+ "F7QxJH": {
+ "defaultMessage": "Name",
+ "description": "CommentForm: Name field label"
+ },
+ "FLkF2R": {
+ "defaultMessage": "All posts in {name}",
+ "description": "TopicPage: posts list title"
+ },
+ "Fj8WFC": {
+ "defaultMessage": "{results, plural, =0 {No articles} one {# article} other {# articles}}",
+ "description": "PostMeta: total found articles"
+ },
+ "FtokGF": {
+ "defaultMessage": "Updated on:",
+ "description": "PostMeta: update date label"
+ },
+ "G/SLvC": {
+ "defaultMessage": "Thanks for your comment! It is now awaiting moderation.",
+ "description": "CommentForm: Comment sent success message"
+ },
+ "GUfnQ4": {
+ "defaultMessage": "Reading time:",
+ "description": "Article meta"
+ },
+ "HriY57": {
+ "defaultMessage": "Thematics",
+ "description": "BlogPage: thematics list widget title"
+ },
+ "Hs+q2V": {
+ "defaultMessage": "Email",
+ "description": "ContactPage: email field label"
+ },
+ "ILRLTq": {
+ "defaultMessage": "{brandingName} picture",
+ "description": "Branding: branding name picture."
+ },
+ "Igx3qp": {
+ "defaultMessage": "Projects",
+ "description": "Breadcrumb: Projects item"
+ },
+ "J4nhm4": {
+ "defaultMessage": "Comment",
+ "description": "CommentForm: Comment field label"
+ },
+ "Ji6xwo": {
+ "defaultMessage": "An error occurred:",
+ "description": "ContactPage: error message"
+ },
+ "KERk7L": {
+ "defaultMessage": "Filter by:",
+ "description": "BlogPage: sidebar title"
+ },
+ "KeRtm/": {
+ "defaultMessage": "Light theme",
+ "description": "Icons: Sun icon (light theme)"
+ },
+ "Kqq2cm": {
+ "defaultMessage": "Load more?",
+ "description": "BlogPage: load more text"
+ },
+ "Mj2BQf": {
+ "defaultMessage": "{name}'s CV",
+ "description": "CVPage: page title"
+ },
+ "N44SOc": {
+ "defaultMessage": "Projects",
+ "description": "HomePage: link to projects"
+ },
+ "N7I4lC": {
+ "defaultMessage": "less than 1 minute",
+ "description": "PostMeta: Reading time value"
+ },
+ "N804XO": {
+ "defaultMessage": "Topics",
+ "description": "SearchPage: topics list widget title"
+ },
+ "Ns8CFb": {
+ "defaultMessage": "Comments",
+ "description": "CommentsList: Comments section title"
+ },
+ "O9XLDc": {
+ "defaultMessage": "Theme:",
+ "description": "ThemeToggle: toggle label"
+ },
+ "OIffB4": {
+ "defaultMessage": "Contact {websiteName} through its website. All you need to do it's to fill the contact form.",
+ "description": "ContactPage: SEO - Meta description"
+ },
+ "OTTv+m": {
+ "defaultMessage": "{count, plural, =0 {Repositories:} one {Repository:} other {Repositories:}}",
+ "description": "ProjectSummary: repositories list label"
+ },
+ "OV9r1K": {
+ "defaultMessage": "Copied!",
+ "description": "Prism: copy button text (clicked)"
+ },
+ "OccTWi": {
+ "defaultMessage": "Page not found",
+ "description": "404Page: page title"
+ },
+ "Oim3rQ": {
+ "defaultMessage": "Email",
+ "description": "CommentForm: Email field label"
+ },
+ "Ox/daH": {
+ "defaultMessage": "{commentCount, plural, =0 {No comments} one {# comment} other {# comments}}",
+ "description": "PostMeta: comment count value"
+ },
+ "P7fxX2": {
+ "defaultMessage": "All posts in {name}",
+ "description": "ThematicPage: posts list title"
+ },
+ "PXp2hv": {
+ "defaultMessage": "{websiteName} | Front-end developer: WordPress/React",
+ "description": "HomePage: SEO - Page title"
+ },
+ "PrIz5o": {
+ "defaultMessage": "Search for a post on {websiteName}",
+ "description": "SearchPage: meta description without query"
+ },
+ "PxMDzL": {
+ "defaultMessage": "Failed to load.",
+ "description": "ThematicsList: failed to load text"
+ },
+ "Qh2CwH": {
+ "defaultMessage": "Find me elsewhere",
+ "description": "ContactPage: social media widget title"
+ },
+ "R0eDmw": {
+ "defaultMessage": "Blog",
+ "description": "BlogPage: breadcrumb item"
+ },
+ "SWq8a4": {
+ "defaultMessage": "Close {type}",
+ "description": "ButtonToolbar: Close button"
+ },
+ "SX1z3t": {
+ "defaultMessage": "Projects: open-source makings - {websiteName}",
+ "description": "ProjectsPage: SEO - Page title"
+ },
+ "T4YA64": {
+ "defaultMessage": "Subscribe",
+ "description": "HomePage: RSS feed subscription text"
+ },
+ "TfU6Qm": {
+ "defaultMessage": "Search",
+ "description": "SearchPage: breadcrumb item"
+ },
+ "U++A+B": {
+ "defaultMessage": "{readingTime, plural, =0 {# minutes} one {# minute} other {# minutes}}",
+ "description": "PostMeta: reading time value"
+ },
+ "U+35YD": {
+ "defaultMessage": "Search",
+ "description": "SearchPage: page title"
+ },
+ "UsQske": {
+ "defaultMessage": "Read more here:",
+ "description": "Sharing: content link prefix"
+ },
+ "VSGuGE": {
+ "defaultMessage": "Search results for {query}",
+ "description": "SearchPage: search results text"
+ },
+ "W2G95o": {
+ "defaultMessage": "Comments:",
+ "description": "PostMeta: comment count label"
+ },
+ "WGFOmA": {
+ "defaultMessage": "Send",
+ "description": "CommentForm: Send button"
+ },
+ "WRkY1/": {
+ "defaultMessage": "Collapse",
+ "description": "ExpandableWidget: collapse text"
+ },
+ "X3PDXO": {
+ "defaultMessage": "Animations:",
+ "description": "ReduceMotion: toggle label"
+ },
+ "Y1ZdJ6": {
+ "defaultMessage": "CV Front-end developer - {websiteName}",
+ "description": "CVPage: SEO - Page title"
+ },
+ "Y3qRib": {
+ "defaultMessage": "Contact form - {websiteName}",
+ "description": "ContactPage: SEO - Page title"
+ },
+ "YEudoh": {
+ "defaultMessage": "Read more articles about:",
+ "description": "PostFooter: read more posts about given subjects"
+ },
+ "Z1eSIz": {
+ "defaultMessage": "Open {type}",
+ "description": "ButtonToolbar: Open button"
+ },
+ "ZJMNRW": {
+ "defaultMessage": "Home",
+ "description": "MainNav: home link"
+ },
+ "ZWh78Y": {
+ "defaultMessage": "Sorry, it seems that the page your are looking for does not exist. If you think this path should work, feel free to <link>contact me</link> with the necessary information so that I can fix the problem.",
+ "description": "404Page: page body"
+ },
+ "Zg4L7U": {
+ "defaultMessage": "Table of contents",
+ "description": "ToC: widget title"
+ },
+ "aQLBE3": {
+ "defaultMessage": "{starsCount, plural, =0 {No stars on Github} one {# star on Github} other {# stars on Github}}",
+ "description": "ProjectPreview: technologies list label"
+ },
+ "agLf5v": {
+ "defaultMessage": "Website:",
+ "description": "PostMeta: website label"
+ },
+ "akSutM": {
+ "defaultMessage": "Projects",
+ "description": "MainNav: projects link"
+ },
+ "azc1GT": {
+ "defaultMessage": "Open menu",
+ "description": "MainNav: open button"
+ },
+ "bBdMGm": {
+ "defaultMessage": "Discover the curriculum of {websiteName}, front-end developer located in France: skills, experiences and training.",
+ "description": "CVPage: SEO - Meta description"
+ },
+ "bHEmkY": {
+ "defaultMessage": "Settings",
+ "description": "Settings: modal title"
+ },
+ "bkbrN7": {
+ "defaultMessage": "Read more<a11y> about {title}</a11y>",
+ "description": "PostPreview: read more link"
+ },
+ "c2NtPj": {
+ "defaultMessage": "Contact",
+ "description": "MainNav: contact link"
+ },
+ "cr2fA4": {
+ "defaultMessage": "Subject",
+ "description": "ContactPage: subject field label"
+ },
+ "dE8xxV": {
+ "defaultMessage": "Close menu",
+ "description": "MainNav: close button"
+ },
+ "dqrd6I": {
+ "defaultMessage": "Back to top",
+ "description": "Footer: Back to top button"
+ },
+ "e9L59q": {
+ "defaultMessage": "No comments yet.",
+ "description": "CommentsList: No comment message"
+ },
+ "eFMu2E": {
+ "defaultMessage": "Search",
+ "description": "SearchForm : form title"
+ },
+ "eUXMG4": {
+ "defaultMessage": "Seen on {domainName}:",
+ "description": "Sharing: seen on text"
+ },
+ "ec3m6p": {
+ "defaultMessage": "Leave a comment",
+ "description": "CommentForm: form title"
+ },
+ "enwhNm": {
+ "defaultMessage": "{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}",
+ "description": "ProjectSummary: technologies list label"
+ },
+ "fGnfqp": {
+ "defaultMessage": "Published on:",
+ "description": "PostMeta: publication date label"
+ },
+ "fOe8rH": {
+ "defaultMessage": "Failed to load.",
+ "description": "SearchPage: failed to load text"
+ },
+ "hKagVG": {
+ "defaultMessage": "License:",
+ "description": "ProjectSummary: license label"
+ },
+ "hV0qHp": {
+ "defaultMessage": "Expand",
+ "description": "ExpandableWidget: expand text"
+ },
+ "hzHuCc": {
+ "defaultMessage": "Reply",
+ "description": "Comment: reply button"
+ },
+ "iqAbyn": {
+ "defaultMessage": "Skip to content",
+ "description": "Layout: Skip to content button"
+ },
+ "iyEh0R": {
+ "defaultMessage": "Failed to load.",
+ "description": "RecentPosts: failed to load text"
+ },
+ "jASD7k": {
+ "defaultMessage": "Linux",
+ "description": "HomePage: link to Linux thematic"
+ },
+ "jGqV2+": {
+ "defaultMessage": "Written by:",
+ "description": "Article meta"
+ },
+ "jN+dY5": {
+ "defaultMessage": "Website",
+ "description": "CommentForm: Website field label"
+ },
+ "jpv+Nz": {
+ "defaultMessage": "Resume",
+ "description": "MainNav: resume link"
+ },
+ "l0+ROl": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}",
+ "description": "PostMeta: thematics list label"
+ },
+ "mC21ht": {
+ "defaultMessage": "Number of articles loaded out of the total available.",
+ "description": "PaginationCursor: loaded articles count aria-label"
+ },
+ "mJLflX": {
+ "defaultMessage": "Send",
+ "description": "ContactPage: send button text"
+ },
+ "mh7tGg": {
+ "defaultMessage": "{title} preview",
+ "description": "ProjectSummary: cover alt text"
+ },
+ "norrGp": {
+ "defaultMessage": "Others thematics",
+ "description": "ThematicPage: thematics list widget title"
+ },
+ "ode0YK": {
+ "defaultMessage": "Dark theme",
+ "description": "Icons: Moon icon (dark theme)"
+ },
+ "okFrAO": {
+ "defaultMessage": "{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}",
+ "description": "ProjectPreview: technologies list label"
+ },
+ "pEtJik": {
+ "defaultMessage": "Load more?",
+ "description": "SearchPage: load more text"
+ },
+ "q3U6uI": {
+ "defaultMessage": "Share",
+ "description": "Sharing: widget title"
+ },
+ "q9cJQe": {
+ "defaultMessage": "Loading...",
+ "description": "Spinner: loading text"
+ },
+ "qPU/Qn": {
+ "defaultMessage": "On",
+ "description": "ReduceMotion: toggle on label"
+ },
+ "qXQETZ": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Related thematics} one {Related thematic} other {Related thematics}}",
+ "description": "RelatedThematics: widget title"
+ },
+ "rXeTkM": {
+ "defaultMessage": "This comment is awaiting moderation.",
+ "description": "Comment: awaiting moderation message"
+ },
+ "s6U1Xt": {
+ "defaultMessage": "Discover {websiteName} projects. Mostly related to web development and open source.",
+ "description": "ProjectsPage: SEO - Meta description"
+ },
+ "sO/Iwj": {
+ "defaultMessage": "Contact me",
+ "description": "HomePage: contact button text"
+ },
+ "soj7do": {
+ "defaultMessage": "Published on:",
+ "description": "Comment: publication date label"
+ },
+ "tMuNTy": {
+ "defaultMessage": "{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.",
+ "description": "HomePage: SEO - Meta description"
+ },
+ "txusHd": {
+ "defaultMessage": "All fields marked with * are required.",
+ "description": "ContactPage: required fields text"
+ },
+ "uHp568": {
+ "defaultMessage": "Name",
+ "description": "ContactPage: name field label"
+ },
+ "ureXFw": {
+ "defaultMessage": "Share on {name}",
+ "description": "Sharing: share on social network text"
+ },
+ "uvB+32": {
+ "defaultMessage": "Discover the legal notice of {websiteName}'s website.",
+ "description": "LegalNoticePage: SEO - Meta description"
+ },
+ "vJ+QDV": {
+ "defaultMessage": "Last updated on:",
+ "description": "ProjectSummary: update date label"
+ },
+ "vK7Sxv": {
+ "defaultMessage": "No results found.",
+ "description": "PostsList: no results"
+ },
+ "vgMk0q": {
+ "defaultMessage": "Popularity:",
+ "description": "ProjectSummary: popularity label"
+ },
+ "vhIggb": {
+ "defaultMessage": "Total:",
+ "description": "Article meta"
+ },
+ "vkF/RP": {
+ "defaultMessage": "Web development",
+ "description": "HomePage: link to web development thematic"
+ },
+ "w/lPUh": {
+ "defaultMessage": "{topicsCount, plural, =0 {Related topics} one {Related topic} other {Related topics}}",
+ "description": "RelatedTopics: widget title"
+ },
+ "w1nIrj": {
+ "defaultMessage": "Off",
+ "description": "ReduceMotion: toggle off label"
+ },
+ "w8GrOf": {
+ "defaultMessage": "Free",
+ "description": "HomePage: link to free thematic"
+ },
+ "x6PPlk": {
+ "defaultMessage": "Message",
+ "description": "ContactPage: message field label"
+ },
+ "xC3Khf": {
+ "defaultMessage": "Download <link>CV in PDF</link>",
+ "description": "CVPreview: download as PDF link"
+ },
+ "yWjXRx": {
+ "defaultMessage": "Legal notice",
+ "description": "FooterNav: legal notice link"
+ },
+ "yfgMcl": {
+ "defaultMessage": "Introduction:",
+ "description": "Sharing: email content prefix"
+ },
+ "ywkCsK": {
+ "defaultMessage": "Error 404",
+ "description": "404Page: breadcrumb item"
+ },
+ "z0ic9c": {
+ "defaultMessage": "Blog",
+ "description": "Breadcrumb: Blog item"
+ },
+ "z9qkcQ": {
+ "defaultMessage": "Use Ctrl+c to copy",
+ "description": "Prism: error text"
+ },
+ "zPJifH": {
+ "defaultMessage": "Blog",
+ "description": "MainNav: blog link"
+ }
+}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
new file mode 100644
index 0000000..23f5278
--- /dev/null
+++ b/src/i18n/fr.json
@@ -0,0 +1,586 @@
+{
+ "+4tiVb": {
+ "defaultMessage": "Others topics",
+ "description": "TopicPage: topics list widget title"
+ },
+ "+COyEW": {
+ "defaultMessage": "{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}",
+ "description": "PostMeta: topics list label"
+ },
+ "+Dre5J": {
+ "defaultMessage": "Open-source projects",
+ "description": "CVPage: social media widget title"
+ },
+ "+Y+tLK": {
+ "defaultMessage": "Blog: development, open source - {websiteName}",
+ "description": "BlogPage: SEO - Page title"
+ },
+ "/IirIt": {
+ "defaultMessage": "Legal notice",
+ "description": "LegalNoticePage: page title"
+ },
+ "/ly3AC": {
+ "defaultMessage": "Copy",
+ "description": "Prism: copy button text (no clicked)"
+ },
+ "00Pf5p": {
+ "defaultMessage": "Failed to load.",
+ "description": "TopicsList: failed to load text"
+ },
+ "16zl9Z": {
+ "defaultMessage": "You are here:",
+ "description": "Breadcrumb: You are here prefix"
+ },
+ "18h/t0": {
+ "defaultMessage": "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.",
+ "description": "BlogPage: SEO - Meta description"
+ },
+ "1h+N2z": {
+ "defaultMessage": "Published on:",
+ "description": "RecentPosts: publication date label"
+ },
+ "2D9tB5": {
+ "defaultMessage": "Topics",
+ "description": "BlogPage: topics list widget title"
+ },
+ "2pykor": {
+ "defaultMessage": "{title} picture",
+ "description": "ProjectPreview: cover alt text"
+ },
+ "310o3F": {
+ "defaultMessage": "Error 404: Page not found - {websiteName}",
+ "description": "404Page: SEO - Page title"
+ },
+ "43OmTY": {
+ "defaultMessage": "Thanks. Your message was successfully sent. I will answer it as soon as possible.",
+ "description": "ContactPage: success message"
+ },
+ "48Ww//": {
+ "defaultMessage": "Page not found.",
+ "description": "404Page: SEO - Meta description"
+ },
+ "4zAUSu": {
+ "defaultMessage": "Legal notice - {websiteName}",
+ "description": "LegalNoticePage: SEO - Page title"
+ },
+ "7TbbIk": {
+ "defaultMessage": "Blog",
+ "description": "BlogPage: page title"
+ },
+ "8Ls2mD": {
+ "defaultMessage": "Please fill the form to contact me.",
+ "description": "ContactPage: page introduction"
+ },
+ "A4LTGq": {
+ "defaultMessage": "Discover search results for {query}",
+ "description": "SearchPage: meta description with query"
+ },
+ "AN9iy7": {
+ "defaultMessage": "Contact",
+ "description": "ContactPage: page title"
+ },
+ "AnaPbu": {
+ "defaultMessage": "Search",
+ "description": "SearchForm: search button text"
+ },
+ "B9OCyV": {
+ "defaultMessage": "Others formats",
+ "description": "CVPage: cv preview widget title"
+ },
+ "C/XGkH": {
+ "defaultMessage": "Failed to load.",
+ "description": "BlogPage: failed to load text"
+ },
+ "CSimmh": {
+ "defaultMessage": "{articlesCount, plural, =0 {# articles} one {# article} other {# articles}} out of a total of {total}",
+ "description": "PaginationCursor: loaded articles count message"
+ },
+ "CT3ydM": {
+ "defaultMessage": "{date} at {time}",
+ "description": "Comment: publication date"
+ },
+ "CWi0go": {
+ "defaultMessage": "Created on:",
+ "description": "ProjectSummary: creation date label"
+ },
+ "CzTbM4": {
+ "defaultMessage": "Contact",
+ "description": "ContactPage: breadcrumb item"
+ },
+ "Dq6+WH": {
+ "defaultMessage": "Thematics",
+ "description": "SearchPage: thematics list widget title"
+ },
+ "Enij19": {
+ "defaultMessage": "Home",
+ "description": "Breadcrumb: Home item"
+ },
+ "EvODgw": {
+ "defaultMessage": "Published on",
+ "description": "PostsList: published on year label"
+ },
+ "F7QxJH": {
+ "defaultMessage": "Name",
+ "description": "CommentForm: Name field label"
+ },
+ "FLkF2R": {
+ "defaultMessage": "All posts in {name}",
+ "description": "TopicPage: posts list title"
+ },
+ "Fj8WFC": {
+ "defaultMessage": "{results, plural, =0 {No articles} one {# article} other {# articles}}",
+ "description": "PostMeta: total found articles"
+ },
+ "FtokGF": {
+ "defaultMessage": "Updated on:",
+ "description": "PostMeta: update date label"
+ },
+ "G/SLvC": {
+ "defaultMessage": "Thanks for your comment! It is now awaiting moderation.",
+ "description": "CommentForm: Comment sent success message"
+ },
+ "GUfnQ4": {
+ "defaultMessage": "Reading time:",
+ "description": "Article meta"
+ },
+ "HriY57": {
+ "defaultMessage": "Thematics",
+ "description": "BlogPage: thematics list widget title"
+ },
+ "Hs+q2V": {
+ "defaultMessage": "Email",
+ "description": "ContactPage: email field label"
+ },
+ "ILRLTq": {
+ "defaultMessage": "{brandingName} picture",
+ "description": "Branding: branding name picture."
+ },
+ "Igx3qp": {
+ "defaultMessage": "Projects",
+ "description": "Breadcrumb: Projects item"
+ },
+ "J4nhm4": {
+ "defaultMessage": "Comment",
+ "description": "CommentForm: Comment field label"
+ },
+ "Ji6xwo": {
+ "defaultMessage": "An error occurred:",
+ "description": "ContactPage: error message"
+ },
+ "KERk7L": {
+ "defaultMessage": "Filter by:",
+ "description": "BlogPage: sidebar title"
+ },
+ "KeRtm/": {
+ "defaultMessage": "Light theme",
+ "description": "Icons: Sun icon (light theme)"
+ },
+ "Kqq2cm": {
+ "defaultMessage": "Load more?",
+ "description": "BlogPage: load more text"
+ },
+ "Mj2BQf": {
+ "defaultMessage": "{name}'s CV",
+ "description": "CVPage: page title"
+ },
+ "N44SOc": {
+ "defaultMessage": "Projects",
+ "description": "HomePage: link to projects"
+ },
+ "N7I4lC": {
+ "defaultMessage": "less than 1 minute",
+ "description": "PostMeta: Reading time value"
+ },
+ "N804XO": {
+ "defaultMessage": "Topics",
+ "description": "SearchPage: topics list widget title"
+ },
+ "Ns8CFb": {
+ "defaultMessage": "Comments",
+ "description": "CommentsList: Comments section title"
+ },
+ "O9XLDc": {
+ "defaultMessage": "Theme:",
+ "description": "ThemeToggle: toggle label"
+ },
+ "OIffB4": {
+ "defaultMessage": "Contact {websiteName} through its website. All you need to do it's to fill the contact form.",
+ "description": "ContactPage: SEO - Meta description"
+ },
+ "OTTv+m": {
+ "defaultMessage": "{count, plural, =0 {Repositories:} one {Repository:} other {Repositories:}}",
+ "description": "ProjectSummary: repositories list label"
+ },
+ "OV9r1K": {
+ "defaultMessage": "Copied!",
+ "description": "Prism: copy button text (clicked)"
+ },
+ "OccTWi": {
+ "defaultMessage": "Page not found",
+ "description": "404Page: page title"
+ },
+ "Oim3rQ": {
+ "defaultMessage": "Email",
+ "description": "CommentForm: Email field label"
+ },
+ "Ox/daH": {
+ "defaultMessage": "{commentCount, plural, =0 {No comments} one {# comment} other {# comments}}",
+ "description": "PostMeta: comment count value"
+ },
+ "P7fxX2": {
+ "defaultMessage": "All posts in {name}",
+ "description": "ThematicPage: posts list title"
+ },
+ "PXp2hv": {
+ "defaultMessage": "{websiteName} | Front-end developer: WordPress/React",
+ "description": "HomePage: SEO - Page title"
+ },
+ "PrIz5o": {
+ "defaultMessage": "Search for a post on {websiteName}",
+ "description": "SearchPage: meta description without query"
+ },
+ "PxMDzL": {
+ "defaultMessage": "Failed to load.",
+ "description": "ThematicsList: failed to load text"
+ },
+ "Qh2CwH": {
+ "defaultMessage": "Find me elsewhere",
+ "description": "ContactPage: social media widget title"
+ },
+ "R0eDmw": {
+ "defaultMessage": "Blog",
+ "description": "BlogPage: breadcrumb item"
+ },
+ "SWq8a4": {
+ "defaultMessage": "Close {type}",
+ "description": "ButtonToolbar: Close button"
+ },
+ "SX1z3t": {
+ "defaultMessage": "Projects: open-source makings - {websiteName}",
+ "description": "ProjectsPage: SEO - Page title"
+ },
+ "T4YA64": {
+ "defaultMessage": "Subscribe",
+ "description": "HomePage: RSS feed subscription text"
+ },
+ "TfU6Qm": {
+ "defaultMessage": "Search",
+ "description": "SearchPage: breadcrumb item"
+ },
+ "U++A+B": {
+ "defaultMessage": "{readingTime, plural, =0 {# minutes} one {# minute} other {# minutes}}",
+ "description": "PostMeta: reading time value"
+ },
+ "U+35YD": {
+ "defaultMessage": "Search",
+ "description": "SearchPage: page title"
+ },
+ "UsQske": {
+ "defaultMessage": "Read more here:",
+ "description": "Sharing: content link prefix"
+ },
+ "VSGuGE": {
+ "defaultMessage": "Search results for {query}",
+ "description": "SearchPage: search results text"
+ },
+ "W2G95o": {
+ "defaultMessage": "Comments:",
+ "description": "PostMeta: comment count label"
+ },
+ "WGFOmA": {
+ "defaultMessage": "Send",
+ "description": "CommentForm: Send button"
+ },
+ "WRkY1/": {
+ "defaultMessage": "Collapse",
+ "description": "ExpandableWidget: collapse text"
+ },
+ "X3PDXO": {
+ "defaultMessage": "Animations:",
+ "description": "ReduceMotion: toggle label"
+ },
+ "Y1ZdJ6": {
+ "defaultMessage": "CV Front-end developer - {websiteName}",
+ "description": "CVPage: SEO - Page title"
+ },
+ "Y3qRib": {
+ "defaultMessage": "Contact form - {websiteName}",
+ "description": "ContactPage: SEO - Page title"
+ },
+ "YEudoh": {
+ "defaultMessage": "Read more articles about:",
+ "description": "PostFooter: read more posts about given subjects"
+ },
+ "Z1eSIz": {
+ "defaultMessage": "Open {type}",
+ "description": "ButtonToolbar: Open button"
+ },
+ "ZJMNRW": {
+ "defaultMessage": "Home",
+ "description": "MainNav: home link"
+ },
+ "ZWh78Y": {
+ "defaultMessage": "Sorry, it seems that the page your are looking for does not exist. If you think this path should work, feel free to <link>contact me</link> with the necessary information so that I can fix the problem.",
+ "description": "404Page: page body"
+ },
+ "Zg4L7U": {
+ "defaultMessage": "Table of contents",
+ "description": "ToC: widget title"
+ },
+ "aQLBE3": {
+ "defaultMessage": "{starsCount, plural, =0 {No stars on Github} one {# star on Github} other {# stars on Github}}",
+ "description": "ProjectPreview: technologies list label"
+ },
+ "agLf5v": {
+ "defaultMessage": "Website:",
+ "description": "PostMeta: website label"
+ },
+ "akSutM": {
+ "defaultMessage": "Projects",
+ "description": "MainNav: projects link"
+ },
+ "azc1GT": {
+ "defaultMessage": "Open menu",
+ "description": "MainNav: open button"
+ },
+ "bBdMGm": {
+ "defaultMessage": "Discover the curriculum of {websiteName}, front-end developer located in France: skills, experiences and training.",
+ "description": "CVPage: SEO - Meta description"
+ },
+ "bHEmkY": {
+ "defaultMessage": "Settings",
+ "description": "Settings: modal title"
+ },
+ "bkbrN7": {
+ "defaultMessage": "Read more<a11y> about {title}</a11y>",
+ "description": "PostPreview: read more link"
+ },
+ "c2NtPj": {
+ "defaultMessage": "Contact",
+ "description": "MainNav: contact link"
+ },
+ "cr2fA4": {
+ "defaultMessage": "Subject",
+ "description": "ContactPage: subject field label"
+ },
+ "dE8xxV": {
+ "defaultMessage": "Close menu",
+ "description": "MainNav: close button"
+ },
+ "dqrd6I": {
+ "defaultMessage": "Back to top",
+ "description": "Footer: Back to top button"
+ },
+ "e9L59q": {
+ "defaultMessage": "No comments yet.",
+ "description": "CommentsList: No comment message"
+ },
+ "eFMu2E": {
+ "defaultMessage": "Search",
+ "description": "SearchForm : form title"
+ },
+ "eUXMG4": {
+ "defaultMessage": "Seen on {domainName}:",
+ "description": "Sharing: seen on text"
+ },
+ "ec3m6p": {
+ "defaultMessage": "Leave a comment",
+ "description": "CommentForm: form title"
+ },
+ "enwhNm": {
+ "defaultMessage": "{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}",
+ "description": "ProjectSummary: technologies list label"
+ },
+ "fGnfqp": {
+ "defaultMessage": "Published on:",
+ "description": "PostMeta: publication date label"
+ },
+ "fOe8rH": {
+ "defaultMessage": "Failed to load.",
+ "description": "SearchPage: failed to load text"
+ },
+ "hKagVG": {
+ "defaultMessage": "License:",
+ "description": "ProjectSummary: license label"
+ },
+ "hV0qHp": {
+ "defaultMessage": "Expand",
+ "description": "ExpandableWidget: expand text"
+ },
+ "hzHuCc": {
+ "defaultMessage": "Reply",
+ "description": "Comment: reply button"
+ },
+ "iqAbyn": {
+ "defaultMessage": "Skip to content",
+ "description": "Layout: Skip to content button"
+ },
+ "iyEh0R": {
+ "defaultMessage": "Failed to load.",
+ "description": "RecentPosts: failed to load text"
+ },
+ "jASD7k": {
+ "defaultMessage": "Linux",
+ "description": "HomePage: link to Linux thematic"
+ },
+ "jGqV2+": {
+ "defaultMessage": "Written by:",
+ "description": "Article meta"
+ },
+ "jN+dY5": {
+ "defaultMessage": "Website",
+ "description": "CommentForm: Website field label"
+ },
+ "jpv+Nz": {
+ "defaultMessage": "Resume",
+ "description": "MainNav: resume link"
+ },
+ "l0+ROl": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}",
+ "description": "PostMeta: thematics list label"
+ },
+ "mC21ht": {
+ "defaultMessage": "Number of articles loaded out of the total available.",
+ "description": "PaginationCursor: loaded articles count aria-label"
+ },
+ "mJLflX": {
+ "defaultMessage": "Send",
+ "description": "ContactPage: send button text"
+ },
+ "mh7tGg": {
+ "defaultMessage": "{title} preview",
+ "description": "ProjectSummary: cover alt text"
+ },
+ "norrGp": {
+ "defaultMessage": "Others thematics",
+ "description": "ThematicPage: thematics list widget title"
+ },
+ "ode0YK": {
+ "defaultMessage": "Dark theme",
+ "description": "Icons: Moon icon (dark theme)"
+ },
+ "okFrAO": {
+ "defaultMessage": "{count, plural, =0 {Technologies:} one {Technology:} other {Technologies:}}",
+ "description": "ProjectPreview: technologies list label"
+ },
+ "pEtJik": {
+ "defaultMessage": "Load more?",
+ "description": "SearchPage: load more text"
+ },
+ "q3U6uI": {
+ "defaultMessage": "Share",
+ "description": "Sharing: widget title"
+ },
+ "q9cJQe": {
+ "defaultMessage": "Loading...",
+ "description": "Spinner: loading text"
+ },
+ "qPU/Qn": {
+ "defaultMessage": "On",
+ "description": "ReduceMotion: toggle on label"
+ },
+ "qXQETZ": {
+ "defaultMessage": "{thematicsCount, plural, =0 {Related thematics} one {Related thematic} other {Related thematics}}",
+ "description": "RelatedThematics: widget title"
+ },
+ "rXeTkM": {
+ "defaultMessage": "This comment is awaiting moderation.",
+ "description": "Comment: awaiting moderation message"
+ },
+ "s6U1Xt": {
+ "defaultMessage": "Discover {websiteName} projects. Mostly related to web development and open source.",
+ "description": "ProjectsPage: SEO - Meta description"
+ },
+ "sO/Iwj": {
+ "defaultMessage": "Contact me",
+ "description": "HomePage: contact button text"
+ },
+ "soj7do": {
+ "defaultMessage": "Published on:",
+ "description": "Comment: publication date label"
+ },
+ "tMuNTy": {
+ "defaultMessage": "{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.",
+ "description": "HomePage: SEO - Meta description"
+ },
+ "txusHd": {
+ "defaultMessage": "All fields marked with * are required.",
+ "description": "ContactPage: required fields text"
+ },
+ "uHp568": {
+ "defaultMessage": "Name",
+ "description": "ContactPage: name field label"
+ },
+ "ureXFw": {
+ "defaultMessage": "Share on {name}",
+ "description": "Sharing: share on social network text"
+ },
+ "uvB+32": {
+ "defaultMessage": "Discover the legal notice of {websiteName}'s website.",
+ "description": "LegalNoticePage: SEO - Meta description"
+ },
+ "vJ+QDV": {
+ "defaultMessage": "Last updated on:",
+ "description": "ProjectSummary: update date label"
+ },
+ "vK7Sxv": {
+ "defaultMessage": "No results found.",
+ "description": "PostsList: no results"
+ },
+ "vgMk0q": {
+ "defaultMessage": "Popularity:",
+ "description": "ProjectSummary: popularity label"
+ },
+ "vhIggb": {
+ "defaultMessage": "Total:",
+ "description": "Article meta"
+ },
+ "vkF/RP": {
+ "defaultMessage": "Web development",
+ "description": "HomePage: link to web development thematic"
+ },
+ "w/lPUh": {
+ "defaultMessage": "{topicsCount, plural, =0 {Related topics} one {Related topic} other {Related topics}}",
+ "description": "RelatedTopics: widget title"
+ },
+ "w1nIrj": {
+ "defaultMessage": "Off",
+ "description": "ReduceMotion: toggle off label"
+ },
+ "w8GrOf": {
+ "defaultMessage": "Free",
+ "description": "HomePage: link to free thematic"
+ },
+ "x6PPlk": {
+ "defaultMessage": "Message",
+ "description": "ContactPage: message field label"
+ },
+ "xC3Khf": {
+ "defaultMessage": "Download <link>CV in PDF</link>",
+ "description": "CVPreview: download as PDF link"
+ },
+ "yWjXRx": {
+ "defaultMessage": "Legal notice",
+ "description": "FooterNav: legal notice link"
+ },
+ "yfgMcl": {
+ "defaultMessage": "Introduction:",
+ "description": "Sharing: email content prefix"
+ },
+ "ywkCsK": {
+ "defaultMessage": "Error 404",
+ "description": "404Page: breadcrumb item"
+ },
+ "z0ic9c": {
+ "defaultMessage": "Blog",
+ "description": "Breadcrumb: Blog item"
+ },
+ "z9qkcQ": {
+ "defaultMessage": "Use Ctrl+c to copy",
+ "description": "Prism: error text"
+ },
+ "zPJifH": {
+ "defaultMessage": "Blog",
+ "description": "MainNav: blog link"
+ }
+}
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 5ba7b95..079dead 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,46 +1,72 @@
import { getLayout } from '@components/Layouts/Layout';
import PostHeader from '@components/PostHeader/PostHeader';
-import { seo } from '@config/seo';
-import { t, Trans } from '@lingui/macro';
+import { config } from '@config/website';
+import styles from '@styles/pages/Page.module.scss';
import { NextPageWithLayout } from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n';
import { GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
import Link from 'next/link';
-import styles from '@styles/pages/Page.module.scss';
+import { FormattedMessage, useIntl } from 'react-intl';
+
+const Error404: NextPageWithLayout = () => {
+ const intl = useIntl();
+
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'Error 404: Page not found - {websiteName}',
+ description: '404Page: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage({
+ defaultMessage: 'Page not found.',
+ description: '404Page: SEO - Meta description',
+ });
-const error404: NextPageWithLayout = () => {
return (
<>
<Head>
- <title>{seo.error404.title}</title>
- <meta name="description" content={seo.error404.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
</Head>
<div className={`${styles.article} ${styles['article--no-comments']}`}>
- <PostHeader title={t`Page not found`} />
+ <PostHeader
+ title={intl.formatMessage({
+ defaultMessage: 'Page not found',
+ description: '404Page: page title',
+ })}
+ />
<div className={styles.body}>
- <Trans>
- Sorry, it seems that the page you are looking for does not exist.
- </Trans>{' '}
- <Trans>
- If you think this path should work, feel free to{' '}
- <Link href="/contact/">contact me</Link> with the necessary
- information so that I can fix the problem.
- </Trans>
+ <FormattedMessage
+ defaultMessage="Sorry, it seems that the page your are looking for does not exist. If you think this path should work, feel free to <link>contact me</link> with the necessary information so that I can fix the problem."
+ description="404Page: page body"
+ values={{
+ link: (chunks: string) => (
+ <Link href="/contact/">
+ <a>{chunks}</a>
+ </Link>
+ ),
+ }}
+ />
</div>
</div>
</>
);
};
-error404.getLayout = getLayout;
+Error404.getLayout = getLayout;
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
- const breadcrumbTitle = t`Error`;
+ const intl = await getIntlInstance();
+ const breadcrumbTitle = intl.formatMessage({
+ defaultMessage: 'Error 404',
+ description: '404Page: breadcrumb item',
+ });
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
@@ -51,4 +77,4 @@ export const getStaticProps: GetStaticProps = async (
};
};
-export default error404;
+export default Error404;
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index db021f9..ec97ff7 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,23 +1,21 @@
-import { useEffect } from 'react';
-import { i18n } from '@lingui/core';
-import { I18nProvider } from '@lingui/react';
+import { config } from '@config/website';
import { AppPropsWithLayout } from '@ts/types/app';
-import { activateLocale, defaultLocale, initLingui } from '@utils/helpers/i18n';
-import '../styles/globals.scss';
import { ThemeProvider } from 'next-themes';
-
-initLingui(defaultLocale);
+import { useRouter } from 'next/router';
+import { IntlProvider } from 'react-intl';
+import '../styles/globals.scss';
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
- const locale: string = pageProps.locale || defaultLocale;
-
- useEffect(() => {
- activateLocale(locale, pageProps.translation);
- });
+ const { locale, defaultLocale } = useRouter();
+ const appLocale: string = locale || config.locales.defaultLocale;
const getLayout = Component.getLayout ?? ((page) => page);
return (
- <I18nProvider i18n={i18n}>
+ <IntlProvider
+ locale={appLocale}
+ defaultLocale={defaultLocale}
+ messages={pageProps.translation}
+ >
<ThemeProvider
defaultTheme="system"
enableColorScheme={true}
@@ -25,7 +23,7 @@ const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
>
{getLayout(<Component {...pageProps} />)}
</ThemeProvider>
- </I18nProvider>
+ </IntlProvider>
);
};
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index d38ff63..8668a66 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -3,11 +3,14 @@ import CommentsList from '@components/CommentsList/CommentsList';
import { getLayout } from '@components/Layouts/Layout';
import PostFooter from '@components/PostFooter/PostFooter';
import PostHeader from '@components/PostHeader/PostHeader';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { Sharing, ToC } from '@components/Widgets';
import { config } from '@config/website';
import { getAllPostsSlug, getPostBySlug } from '@services/graphql/queries';
+import styles from '@styles/pages/Page.module.scss';
import { NextPageWithLayout } from '@ts/types/app';
import { ArticleMeta, ArticleProps } from '@ts/types/articles';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { loadTranslation } from '@utils/helpers/i18n';
import { addPrismClasses, translateCopyButton } from '@utils/helpers/prism';
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
@@ -15,9 +18,7 @@ import { useRouter } from 'next/router';
import Prism from 'prismjs';
import { ParsedUrlQuery } from 'querystring';
import { useEffect } from 'react';
-import styles from '@styles/pages/Page.module.scss';
-import { Sharing, ToC } from '@components/Widgets';
-import Sidebar from '@components/Sidebar/Sidebar';
+import { useIntl } from 'react-intl';
import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts';
const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
@@ -45,6 +46,7 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
wordsCount: info.wordsCount,
};
+ const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : config.locales.defaultLocale;
const articleUrl = `${config.url}${router.asPath}`;
@@ -55,8 +57,8 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
});
useEffect(() => {
- translateCopyButton(locale);
- }, [locale]);
+ translateCopyButton(locale, intl);
+ }, [intl, locale]);
const webpageSchema: WebPage = {
'@id': `${articleUrl}`,
@@ -163,7 +165,7 @@ export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
const { slug } = context.params as PostParams;
const post = await getPostBySlug(slug);
const breadcrumbTitle = post.title;
@@ -171,7 +173,6 @@ export const getStaticProps: GetStaticProps = async (
return {
props: {
breadcrumbTitle,
- locale,
post,
translation,
},
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 0650cfb..9a86d9f 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -1,27 +1,27 @@
-import { GetStaticProps, GetStaticPropsContext } from 'next';
-import Head from 'next/head';
-import { t } from '@lingui/macro';
-import { getLayout } from '@components/Layouts/Layout';
-import { seo } from '@config/seo';
-import { config } from '@config/website';
-import { NextPageWithLayout } from '@ts/types/app';
-import { BlogPageProps, PostsList as PostsListData } from '@ts/types/blog';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
-import PostsList from '@components/PostsList/PostsList';
-import useSWRInfinite from 'swr/infinite';
import { Button } from '@components/Buttons';
-import { getPublishedPosts } from '@services/graphql/queries';
+import { getLayout } from '@components/Layouts/Layout';
+import PaginationCursor from '@components/PaginationCursor/PaginationCursor';
import PostHeader from '@components/PostHeader/PostHeader';
-import { ThematicsList, TopicsList } from '@components/Widgets';
+import PostsList from '@components/PostsList/PostsList';
import Sidebar from '@components/Sidebar/Sidebar';
+import Spinner from '@components/Spinner/Spinner';
+import { ThematicsList, TopicsList } from '@components/Widgets';
+import { config } from '@config/website';
+import { getPublishedPosts } from '@services/graphql/queries';
import styles from '@styles/pages/Page.module.scss';
+import { NextPageWithLayout } from '@ts/types/app';
+import { BlogPageProps, PostsList as PostsListData } from '@ts/types/blog';
+import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';
-import Spinner from '@components/Spinner/Spinner';
+import { useIntl } from 'react-intl';
import { Blog as BlogSchema, Graph, WebPage } from 'schema-dts';
-import { useRouter } from 'next/router';
-import PaginationCursor from '@components/PaginationCursor/PaginationCursor';
+import useSWRInfinite from 'swr/infinite';
const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
+ const intl = useIntl();
const lastPostRef = useRef<HTMLSpanElement>(null);
const router = useRouter();
@@ -76,21 +76,39 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
};
const getPostsList = () => {
- if (error) return t`Failed to load.`;
+ if (error)
+ return intl.formatMessage({
+ defaultMessage: 'Failed to load.',
+ description: 'BlogPage: failed to load text',
+ });
if (!data) return <Spinner />;
return <PostsList ref={lastPostRef} data={data} showYears={true} />;
};
- const title = t`Blog`;
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'Blog: development, open source - {websiteName}',
+ description: 'BlogPage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.",
+ description: 'BlogPage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
const pageUrl = `${config.url}${router.asPath}`;
const webpageSchema: WebPage = {
'@id': `${pageUrl}`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.blog.title,
- description: seo.blog.description,
+ name: pageTitle,
+ description: pageDescription,
inLanguage: config.locales.defaultLocale,
reviewedBy: { '@id': `${config.url}/#branding` },
url: `${config.url}`,
@@ -115,15 +133,20 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
'@graph': [webpageSchema, blogSchema],
};
+ const title = intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'BlogPage: page title',
+ });
+
return (
<>
<Head>
- <title>{seo.blog.title}</title>
- <meta name="description" content={seo.blog.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
- <meta property="og:description" content={seo.blog.description} />
+ <meta property="og:description" content={pageDescription} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
@@ -146,13 +169,34 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
isDisabled={isLoadingMore}
clickHandler={loadMorePosts}
position="center"
- >{t`Load more?`}</Button>
+ >
+ {intl.formatMessage({
+ defaultMessage: 'Load more?',
+ description: 'BlogPage: load more text',
+ })}
+ </Button>
</>
)}
</div>
- <Sidebar position="right" title={t`Filter by`}>
- <ThematicsList title={t`Thematics`} />
- <TopicsList title={t`Topics`} />
+ <Sidebar
+ position="right"
+ title={intl.formatMessage({
+ defaultMessage: 'Filter by:',
+ description: 'BlogPage: sidebar title',
+ })}
+ >
+ <ThematicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Thematics',
+ description: 'BlogPage: thematics list widget title',
+ })}
+ />
+ <TopicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Topics',
+ description: 'BlogPage: topics list widget title',
+ })}
+ />
</Sidebar>
</article>
</>
@@ -164,10 +208,14 @@ Blog.getLayout = getLayout;
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
- const breadcrumbTitle = t`Blog`;
+ const intl = await getIntlInstance();
+ const breadcrumbTitle = intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'BlogPage: breadcrumb item',
+ });
const data = await getPublishedPosts({ first: config.postsPerPage });
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index cb88b7d..489135d 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -1,23 +1,23 @@
import { ButtonSubmit } from '@components/Buttons';
import { Form, FormItem, Input, TextArea } from '@components/Form';
import { getLayout } from '@components/Layouts/Layout';
-import { seo } from '@config/seo';
-import { t } from '@lingui/macro';
+import PostHeader from '@components/PostHeader/PostHeader';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { SocialMedia } from '@components/Widgets';
+import { config } from '@config/website';
import { sendMail } from '@services/graphql/mutations';
+import styles from '@styles/pages/Page.module.scss';
import { NextPageWithLayout } from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n';
import { GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
+import { useRouter } from 'next/router';
import { FormEvent, useState } from 'react';
-import PostHeader from '@components/PostHeader/PostHeader';
-import styles from '@styles/pages/Page.module.scss';
-import { SocialMedia } from '@components/Widgets';
-import Sidebar from '@components/Sidebar/Sidebar';
+import { useIntl } from 'react-intl';
import { ContactPage as ContactPageSchema, Graph, WebPage } from 'schema-dts';
-import { config } from '@config/website';
-import { useRouter } from 'next/router';
const ContactPage: NextPageWithLayout = () => {
+ const intl = useIntl();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [subject, setSubject] = useState('');
@@ -46,26 +46,54 @@ const ContactPage: NextPageWithLayout = () => {
if (mail.sent) {
setStatus(
- t`Thanks. Your message was successfully sent. I will answer it as soon as possible.`
+ intl.formatMessage({
+ defaultMessage:
+ 'Thanks. Your message was successfully sent. I will answer it as soon as possible.',
+ description: 'ContactPage: success message',
+ })
);
resetForm();
} else {
- const errorPrefix = t`An error occurred:`;
+ const errorPrefix = intl.formatMessage({
+ defaultMessage: 'An error occurred:',
+ description: 'ContactPage: error message',
+ });
const error = `${errorPrefix} ${mail.message}`;
setStatus(error);
}
};
- const title = t`Contact`;
- const intro = t`Please fill the form to contact me.`;
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'Contact form - {websiteName}',
+ description: 'ContactPage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ "Contact {websiteName} through its website. All you need to do it's to fill the contact form.",
+ description: 'ContactPage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
const pageUrl = `${config.url}${router.asPath}`;
+ const title = intl.formatMessage({
+ defaultMessage: 'Contact',
+ description: 'ContactPage: page title',
+ });
+ const intro = intl.formatMessage({
+ defaultMessage: 'Please fill the form to contact me.',
+ description: 'ContactPage: page introduction',
+ });
const webpageSchema: WebPage = {
'@id': `${pageUrl}`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.contact.title,
- description: seo.contact.description,
+ name: pageTitle,
+ description: pageDescription,
reviewedBy: { '@id': `${config.url}/#branding` },
url: `${pageUrl}`,
isPartOf: {
@@ -94,8 +122,8 @@ const ContactPage: NextPageWithLayout = () => {
return (
<>
<Head>
- <title>{seo.contact.title}</title>
- <meta name="description" content={seo.contact.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
@@ -111,7 +139,12 @@ const ContactPage: NextPageWithLayout = () => {
>
<PostHeader title={title} intro={intro} />
<div className={styles.body}>
- <p>{t`All fields marked with * are required.`}</p>
+ <p>
+ {intl.formatMessage({
+ defaultMessage: 'All fields marked with * are required.',
+ description: 'ContactPage: required fields text',
+ })}
+ </p>
{status && <p>{status}</p>}
<Form submitHandler={submitHandler}>
<FormItem>
@@ -120,7 +153,10 @@ const ContactPage: NextPageWithLayout = () => {
name="name"
value={name}
setValue={setName}
- label={t`Name`}
+ label={intl.formatMessage({
+ defaultMessage: 'Name',
+ description: 'ContactPage: name field label',
+ })}
required={true}
/>
</FormItem>
@@ -130,7 +166,10 @@ const ContactPage: NextPageWithLayout = () => {
name="email"
value={email}
setValue={setEmail}
- label={t`Email`}
+ label={intl.formatMessage({
+ defaultMessage: 'Email',
+ description: 'ContactPage: email field label',
+ })}
required={true}
/>
</FormItem>
@@ -140,7 +179,10 @@ const ContactPage: NextPageWithLayout = () => {
name="subject"
value={subject}
setValue={setSubject}
- label={t`Subject`}
+ label={intl.formatMessage({
+ defaultMessage: 'Subject',
+ description: 'ContactPage: subject field label',
+ })}
/>
</FormItem>
<FormItem>
@@ -149,18 +191,29 @@ const ContactPage: NextPageWithLayout = () => {
name="message"
value={message}
setValue={setMessage}
- label={t`Message`}
+ label={intl.formatMessage({
+ defaultMessage: 'Message',
+ description: 'ContactPage: message field label',
+ })}
required={true}
/>
</FormItem>
<FormItem>
- <ButtonSubmit>{t`Send`}</ButtonSubmit>
+ <ButtonSubmit>
+ {intl.formatMessage({
+ defaultMessage: 'Send',
+ description: 'ContactPage: send button text',
+ })}
+ </ButtonSubmit>
</FormItem>
</Form>
</div>
<Sidebar position="right">
<SocialMedia
- title={t`Find me elsewhere`}
+ title={intl.formatMessage({
+ defaultMessage: 'Find me elsewhere',
+ description: 'ContactPage: social media widget title',
+ })}
github={true}
gitlab={true}
linkedin={true}
@@ -176,9 +229,13 @@ ContactPage.getLayout = getLayout;
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
- const breadcrumbTitle = t`Contact`;
+ const intl = await getIntlInstance();
+ const breadcrumbTitle = intl.formatMessage({
+ defaultMessage: 'Contact',
+ description: 'ContactPage: breadcrumb item',
+ });
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index 85bddd6..c3686de 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -1,21 +1,21 @@
import { getLayout } from '@components/Layouts/Layout';
-import { seo } from '@config/seo';
-import { NextPageWithLayout } from '@ts/types/app';
-import { GetStaticProps, GetStaticPropsContext } from 'next';
-import Head from 'next/head';
-import CVContent, { intro, meta, pdf, image } from '@content/pages/cv.mdx';
import PostHeader from '@components/PostHeader/PostHeader';
-import { ArticleMeta } from '@ts/types/articles';
-import styles from '@styles/pages/Page.module.scss';
-import { CVPreview, SocialMedia, ToC } from '@components/Widgets';
-import { t } from '@lingui/macro';
import Sidebar from '@components/Sidebar/Sidebar';
-import { AboutPage, Graph, WebPage } from 'schema-dts';
+import { CVPreview, SocialMedia, ToC } from '@components/Widgets';
import { config } from '@config/website';
+import CVContent, { intro, meta, pdf, image } from '@content/pages/cv.mdx';
+import styles from '@styles/pages/Page.module.scss';
+import { NextPageWithLayout } from '@ts/types/app';
+import { ArticleMeta } from '@ts/types/articles';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
import { useRouter } from 'next/router';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { useIntl } from 'react-intl';
+import { AboutPage, Graph, WebPage } from 'schema-dts';
const CV: NextPageWithLayout = () => {
+ const intl = useIntl();
const router = useRouter();
const dates = {
publication: meta.publishedOn,
@@ -26,13 +26,28 @@ const CV: NextPageWithLayout = () => {
dates,
};
const pageUrl = `${config.url}${router.asPath}`;
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'CV Front-end developer - {websiteName}',
+ description: 'CVPage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ 'Discover the curriculum of {websiteName}, front-end developer located in France: skills, experiences and training.',
+ description: 'CVPage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
const webpageSchema: WebPage = {
'@id': `${pageUrl}`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.cv.title,
- description: seo.cv.description,
+ name: pageTitle,
+ description: pageDescription,
reviewedBy: { '@id': `${config.url}/#branding` },
url: `${pageUrl}`,
isPartOf: {
@@ -46,7 +61,7 @@ const CV: NextPageWithLayout = () => {
const cvSchema: AboutPage = {
'@id': `${config.url}/#cv`,
'@type': 'AboutPage',
- name: `${config.name} CV`,
+ name: pageTitle,
description: intro,
author: { '@id': `${config.url}/#branding` },
creator: { '@id': `${config.url}/#branding` },
@@ -66,17 +81,25 @@ const CV: NextPageWithLayout = () => {
'@graph': [webpageSchema, cvSchema],
};
+ const title = intl.formatMessage(
+ {
+ defaultMessage: "{name}'s CV",
+ description: 'CVPage: page title',
+ },
+ { name: config.name }
+ );
+
return (
<>
<Head>
- <title>{seo.cv.title}</title>
- <meta name="description" content={seo.cv.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="article" />
- <meta property="og:title" content={`${config.name} CV`} />
+ <meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
<meta property="og:image" content={image} />
- <meta property="og:image:alt" content={`${config.name} CV`} />
+ <meta property="og:image:alt" content={title} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
@@ -94,9 +117,19 @@ const CV: NextPageWithLayout = () => {
<CVContent />
</div>
<Sidebar position="right">
- <CVPreview title={t`Other formats`} imgSrc={image} pdf={pdf} />
+ <CVPreview
+ title={intl.formatMessage({
+ defaultMessage: 'Others formats',
+ description: 'CVPage: cv preview widget title',
+ })}
+ imgSrc={image}
+ pdf={pdf}
+ />
<SocialMedia
- title={t`Open-source projects`}
+ title={intl.formatMessage({
+ defaultMessage: 'Open-source projects',
+ description: 'CVPage: social media widget title',
+ })}
github={true}
gitlab={true}
/>
@@ -113,7 +146,7 @@ export const getStaticProps: GetStaticProps = async (
) => {
const breadcrumbTitle = meta.title;
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index ae5fe4b..41a4603 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,30 +1,39 @@
-import type { ReactElement } from 'react';
-import { GetStaticProps, GetStaticPropsContext } from 'next';
-import Head from 'next/head';
+import FeedIcon from '@assets/images/icon-feed.svg';
+import { ButtonLink } from '@components/Buttons';
+import { ContactIcon } from '@components/Icons';
import Layout from '@components/Layouts/Layout';
-import { seo } from '@config/seo';
-import { NextPageWithLayout } from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { config } from '@config/website';
import HomePageContent from '@content/pages/homepage.mdx';
-import { ButtonLink } from '@components/Buttons';
import styles from '@styles/pages/Home.module.scss';
-import { t } from '@lingui/macro';
-import FeedIcon from '@assets/images/icon-feed.svg';
-import { ContactIcon } from '@components/Icons';
+import { NextPageWithLayout } from '@ts/types/app';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
+import type { ReactElement } from 'react';
+import { useIntl } from 'react-intl';
import { Graph, WebPage } from 'schema-dts';
-import { config } from '@config/website';
const Home: NextPageWithLayout = () => {
+ const intl = useIntl();
+
const CodingLinks = () => {
return (
<ul className={styles['links-list']}>
<li>
<ButtonLink target="/thematique/developpement-web">
- {t`Web development`}
+ {intl.formatMessage({
+ defaultMessage: 'Web development',
+ description: 'HomePage: link to web development thematic',
+ })}
</ButtonLink>
</li>
<li>
- <ButtonLink target="/projets">{t`Projects`}</ButtonLink>
+ <ButtonLink target="/projets">
+ {intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'HomePage: link to projects',
+ })}
+ </ButtonLink>
</li>
</ul>
);
@@ -57,10 +66,20 @@ const Home: NextPageWithLayout = () => {
return (
<ul className={styles['links-list']}>
<li>
- <ButtonLink target="/thematique/libre">{t`Free`}</ButtonLink>
+ <ButtonLink target="/thematique/libre">
+ {intl.formatMessage({
+ defaultMessage: 'Free',
+ description: 'HomePage: link to free thematic',
+ })}
+ </ButtonLink>
</li>
<li>
- <ButtonLink target="/thematique/linux">{t`Linux`}</ButtonLink>
+ <ButtonLink target="/thematique/linux">
+ {intl.formatMessage({
+ defaultMessage: 'Linux',
+ description: 'HomePage: link to Linux thematic',
+ })}
+ </ButtonLink>
</li>
</ul>
);
@@ -72,13 +91,19 @@ const Home: NextPageWithLayout = () => {
<li>
<ButtonLink target="/contact">
<ContactIcon />
- {t`Contact me`}
+ {intl.formatMessage({
+ defaultMessage: 'Contact me',
+ description: 'HomePage: contact button text',
+ })}
</ButtonLink>
</li>
<li>
<ButtonLink target="/feed">
<FeedIcon className={styles['icon--feed']} />
- {t`Subscribe`}
+ {intl.formatMessage({
+ defaultMessage: 'Subscribe',
+ description: 'HomePage: RSS feed subscription text',
+ })}
</ButtonLink>
</li>
</ul>
@@ -92,12 +117,28 @@ const Home: NextPageWithLayout = () => {
MoreLinks: MoreLinks,
};
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: '{websiteName} | Front-end developer: WordPress/React',
+ description: 'HomePage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ '{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.',
+ description: 'HomePage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
+
const webpageSchema: WebPage = {
'@id': `${config.url}/#home`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.legalNotice.title,
- description: seo.legalNotice.description,
+ name: pageTitle,
+ description: pageDescription,
author: { '@id': `${config.url}/#branding` },
creator: { '@id': `${config.url}/#branding` },
editor: { '@id': `${config.url}/#branding` },
@@ -115,12 +156,12 @@ const Home: NextPageWithLayout = () => {
return (
<>
<Head>
- <title>{seo.homepage.title}</title>
- <meta name="description" content={seo.homepage.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:type" content="website" />
<meta property="og:url" content={`${config.url}`} />
- <meta property="og:title" content={seo.homepage.title} />
- <meta property="og:description" content={seo.homepage.description} />
+ <meta property="og:title" content={pageTitle} />
+ <meta property="og:description" content={pageDescription} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
@@ -141,11 +182,10 @@ export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
- locale,
translation,
},
};
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index c9d2ccd..0ec92a2 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -1,24 +1,24 @@
import { getLayout } from '@components/Layouts/Layout';
-import { seo } from '@config/seo';
-import { NextPageWithLayout } from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
-import { GetStaticProps, GetStaticPropsContext } from 'next';
-import Head from 'next/head';
+import PostHeader from '@components/PostHeader/PostHeader';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { ToC } from '@components/Widgets';
+import { config } from '@config/website';
import LegalNoticeContent, {
intro,
meta,
} from '@content/pages/legal-notice.mdx';
-import PostHeader from '@components/PostHeader/PostHeader';
-import { ArticleMeta } from '@ts/types/articles';
import styles from '@styles/pages/Page.module.scss';
-import { ToC } from '@components/Widgets';
-import Sidebar from '@components/Sidebar/Sidebar';
-import { Article, Graph, WebPage } from 'schema-dts';
-import { config } from '@config/website';
+import { NextPageWithLayout } from '@ts/types/app';
+import { ArticleMeta } from '@ts/types/articles';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
import { useRouter } from 'next/router';
-import { t } from '@lingui/macro';
+import { useIntl } from 'react-intl';
+import { Article, Graph, WebPage } from 'schema-dts';
const LegalNotice: NextPageWithLayout = () => {
+ const intl = useIntl();
const router = useRouter();
const dates = {
publication: meta.publishedOn,
@@ -28,8 +28,25 @@ const LegalNotice: NextPageWithLayout = () => {
const pageMeta: ArticleMeta = {
dates,
};
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'Legal notice - {websiteName}',
+ description: 'LegalNoticePage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage: "Discover the legal notice of {websiteName}'s website.",
+ description: 'LegalNoticePage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
const pageUrl = `${config.url}${router.asPath}`;
-
+ const title = intl.formatMessage({
+ defaultMessage: 'Legal notice',
+ description: 'LegalNoticePage: page title',
+ });
const publicationDate = new Date(dates.publication);
const updateDate = new Date(dates.update);
@@ -37,8 +54,8 @@ const LegalNotice: NextPageWithLayout = () => {
'@id': `${pageUrl}`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.legalNotice.title,
- description: seo.legalNotice.description,
+ name: pageTitle,
+ description: pageDescription,
inLanguage: config.locales.defaultLocale,
license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
reviewedBy: { '@id': `${config.url}/#branding` },
@@ -51,7 +68,7 @@ const LegalNotice: NextPageWithLayout = () => {
const articleSchema: Article = {
'@id': `${config.url}/#legal-notice`,
'@type': 'Article',
- name: t`Legal notice`,
+ name: title,
description: intro,
author: { '@id': `${config.url}/#branding` },
copyrightYear: publicationDate.getFullYear(),
@@ -73,11 +90,11 @@ const LegalNotice: NextPageWithLayout = () => {
return (
<>
<Head>
- <title>{seo.legalNotice.title}</title>
- <meta name="description" content={seo.legalNotice.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="article" />
- <meta property="og:title" content={t`Legal notice`} />
+ <meta property="og:title" content={pageTitle} />
<meta property="og:description" content={intro} />
<script
type="application/ld+json"
@@ -107,7 +124,7 @@ export const getStaticProps: GetStaticProps = async (
) => {
const breadcrumbTitle = meta.title;
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/projet/[slug].tsx b/src/pages/projet/[slug].tsx
index 847f84c..14ddf9c 100644
--- a/src/pages/projet/[slug].tsx
+++ b/src/pages/projet/[slug].tsx
@@ -11,7 +11,7 @@ import {
Project as ProjectData,
ProjectProps,
} from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { loadTranslation } from '@utils/helpers/i18n';
import {
getAllProjectsFilename,
getProjectData,
@@ -133,7 +133,7 @@ export const getStaticProps: GetStaticProps = async (
) => {
const breadcrumbTitle = '';
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
const { slug } = context.params as ProjectParams;
const project = await getProjectData(slug);
diff --git a/src/pages/projets.tsx b/src/pages/projets.tsx
index 4359721..da4523c 100644
--- a/src/pages/projets.tsx
+++ b/src/pages/projets.tsx
@@ -1,19 +1,20 @@
import { getLayout } from '@components/Layouts/Layout';
import PostHeader from '@components/PostHeader/PostHeader';
import ProjectsList from '@components/ProjectsList/ProjectsList';
-import { seo } from '@config/seo';
import { config } from '@config/website';
import PageContent, { meta } from '@content/pages/projects.mdx';
import styles from '@styles/pages/Projects.module.scss';
import { Project } from '@ts/types/app';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { loadTranslation } from '@utils/helpers/i18n';
import { getSortedProjects } from '@utils/helpers/projects';
import { GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
import { Article, Graph, WebPage } from 'schema-dts';
const Projects = ({ projects }: { projects: Project[] }) => {
+ const intl = useIntl();
const dates = {
publication: meta.publishedOn,
update: meta.updatedOn,
@@ -22,13 +23,28 @@ const Projects = ({ projects }: { projects: Project[] }) => {
const updateDate = new Date(dates.update);
const router = useRouter();
const pageUrl = `${config.url}${router.asPath}`;
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: 'Projects: open-source makings - {websiteName}',
+ description: 'ProjectsPage: SEO - Page title',
+ },
+ { websiteName: config.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ 'Discover {websiteName} projects. Mostly related to web development and open source.',
+ description: 'ProjectsPage: SEO - Meta description',
+ },
+ { websiteName: config.name }
+ );
const webpageSchema: WebPage = {
'@id': `${pageUrl}`,
'@type': 'WebPage',
breadcrumb: { '@id': `${config.url}/#breadcrumb` },
- name: seo.legalNotice.title,
- description: seo.legalNotice.description,
+ name: pageTitle,
+ description: pageDescription,
inLanguage: config.locales.defaultLocale,
license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
reviewedBy: { '@id': `${config.url}/#branding` },
@@ -42,7 +58,7 @@ const Projects = ({ projects }: { projects: Project[] }) => {
'@id': `${config.url}/#projects`,
'@type': 'Article',
name: meta.title,
- description: seo.projects.description,
+ description: pageDescription,
author: { '@id': `${config.url}/#branding` },
copyrightYear: publicationDate.getFullYear(),
creator: { '@id': `${config.url}/#branding` },
@@ -63,12 +79,12 @@ const Projects = ({ projects }: { projects: Project[] }) => {
return (
<>
<Head>
- <title>{seo.projects.title}</title>
- <meta name="description" content={seo.projects.description} />
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={meta.title} />
- <meta property="og:description" content={seo.projects.description} />
+ <meta property="og:description" content={pageDescription} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
@@ -92,7 +108,7 @@ export const getStaticProps: GetStaticProps = async (
const breadcrumbTitle = meta.title;
const { locale } = context;
const projects: Project[] = await getSortedProjects();
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index 7f410e8..857b114 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -1,25 +1,26 @@
import { Button } from '@components/Buttons';
import { getLayout } from '@components/Layouts/Layout';
+import PaginationCursor from '@components/PaginationCursor/PaginationCursor';
import PostHeader from '@components/PostHeader/PostHeader';
import PostsList from '@components/PostsList/PostsList';
+import Sidebar from '@components/Sidebar/Sidebar';
+import Spinner from '@components/Spinner/Spinner';
+import { ThematicsList, TopicsList } from '@components/Widgets';
import { config } from '@config/website';
-import { t } from '@lingui/macro';
import { getPublishedPosts } from '@services/graphql/queries';
+import styles from '@styles/pages/Page.module.scss';
import { NextPageWithLayout } from '@ts/types/app';
import { PostsList as PostsListData } from '@ts/types/blog';
+import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n';
import { GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';
+import { useIntl } from 'react-intl';
import useSWRInfinite from 'swr/infinite';
-import Sidebar from '@components/Sidebar/Sidebar';
-import { ThematicsList, TopicsList } from '@components/Widgets';
-import styles from '@styles/pages/Page.module.scss';
-import Spinner from '@components/Spinner/Spinner';
-import PaginationCursor from '@components/PaginationCursor/PaginationCursor';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
const Search: NextPageWithLayout = () => {
+ const intl = useIntl();
const [query, setQuery] = useState('');
const router = useRouter();
const lastPostRef = useRef<HTMLSpanElement>(null);
@@ -76,15 +77,33 @@ const Search: NextPageWithLayout = () => {
const hasNextPage = data && data[data.length - 1].pageInfo.hasNextPage;
const title = query
- ? t`Search results for: ${query}`
- : t({
- comment: 'Search page title',
- message: 'Search',
+ ? intl.formatMessage(
+ {
+ defaultMessage: 'Search results for {query}',
+ description: 'SearchPage: search results text',
+ },
+ { query }
+ )
+ : intl.formatMessage({
+ defaultMessage: 'Search',
+ description: 'SearchPage: page title',
});
const description = query
- ? t`Discover search results for: ${query}`
- : t`Search for a post on ${config.name}.`;
+ ? intl.formatMessage(
+ {
+ defaultMessage: 'Discover search results for {query}',
+ description: 'SearchPage: meta description with query',
+ },
+ { query }
+ )
+ : intl.formatMessage(
+ {
+ defaultMessage: 'Search for a post on {websiteName}',
+ description: 'SearchPage: meta description without query',
+ },
+ { websiteName: config.name }
+ );
const head = {
title: `${title} | ${config.name}`,
@@ -99,7 +118,11 @@ const Search: NextPageWithLayout = () => {
};
const getPostsList = () => {
- if (error) return t`Failed to load.`;
+ if (error)
+ return intl.formatMessage({
+ defaultMessage: 'Failed to load.',
+ description: 'SearchPage: failed to load text',
+ });
if (!data) return <Spinner />;
return <PostsList ref={lastPostRef} data={data} showYears={false} />;
@@ -127,13 +150,28 @@ const Search: NextPageWithLayout = () => {
isDisabled={isLoadingMore}
clickHandler={loadMorePosts}
position="center"
- >{t`Load more?`}</Button>
+ >
+ {intl.formatMessage({
+ defaultMessage: 'Load more?',
+ description: 'SearchPage: load more text',
+ })}
+ </Button>
</>
)}
</div>
<Sidebar position="right">
- <ThematicsList title={t`Thematics`} />
- <TopicsList title={t`Topics`} />
+ <ThematicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Thematics',
+ description: 'SearchPage: thematics list widget title',
+ })}
+ />
+ <TopicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Topics',
+ description: 'SearchPage: topics list widget title',
+ })}
+ />
</Sidebar>
</article>
</>
@@ -145,9 +183,13 @@ Search.getLayout = getLayout;
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
- const breadcrumbTitle = t`Search`;
+ const intl = await getIntlInstance();
+ const breadcrumbTitle = intl.formatMessage({
+ defaultMessage: 'Search',
+ description: 'SearchPage: breadcrumb item',
+ });
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
return {
props: {
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 9947758..87a86a2 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -1,24 +1,25 @@
import { getLayout } from '@components/Layouts/Layout';
+import PostHeader from '@components/PostHeader/PostHeader';
import PostPreview from '@components/PostPreview/PostPreview';
-import { t } from '@lingui/macro';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { RelatedThematics, ToC, TopicsList } from '@components/Widgets';
+import { config } from '@config/website';
+import { getAllTopicsSlug, getTopicBySlug } from '@services/graphql/queries';
+import styles from '@styles/pages/Page.module.scss';
import { NextPageWithLayout } from '@ts/types/app';
+import { ArticleMeta } from '@ts/types/articles';
import { TopicProps, ThematicPreview } from '@ts/types/taxonomies';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
+import { loadTranslation } from '@utils/helpers/i18n';
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
import { ParsedUrlQuery } from 'querystring';
-import styles from '@styles/pages/Page.module.scss';
-import { getAllTopicsSlug, getTopicBySlug } from '@services/graphql/queries';
-import PostHeader from '@components/PostHeader/PostHeader';
-import { ArticleMeta } from '@ts/types/articles';
-import { RelatedThematics, ToC, TopicsList } from '@components/Widgets';
import { useRef } from 'react';
-import Head from 'next/head';
-import Sidebar from '@components/Sidebar/Sidebar';
+import { useIntl } from 'react-intl';
import { Article as Article, Graph, WebPage } from 'schema-dts';
-import { config } from '@config/website';
-import { useRouter } from 'next/router';
const Topic: NextPageWithLayout<TopicProps> = ({ topic }) => {
+ const intl = useIntl();
const relatedThematics = useRef<ThematicPreview[]>([]);
const router = useRouter();
@@ -128,14 +129,27 @@ const Topic: NextPageWithLayout<TopicProps> = ({ topic }) => {
<div dangerouslySetInnerHTML={{ __html: topic.content }}></div>
{topic.posts.length > 0 && (
<section className={styles.section}>
- <h2>{t`All posts in ${topic.title}`}</h2>
+ <h2>
+ {intl.formatMessage(
+ {
+ defaultMessage: 'All posts in {name}',
+ description: 'TopicPage: posts list title',
+ },
+ { name: topic.title }
+ )}
+ </h2>
<ol className={styles.list}>{getPostsList()}</ol>
</section>
)}
</div>
<Sidebar position="right">
<RelatedThematics thematics={relatedThematics.current} />
- <TopicsList title={t`Other topics`} />
+ <TopicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Others topics',
+ description: 'TopicPage: topics list widget title',
+ })}
+ />
</Sidebar>
</article>
</>
@@ -152,7 +166,7 @@ export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
const { slug } = context.params as PostParams;
const topic = await getTopicBySlug(slug);
const breadcrumbTitle = topic.title;
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index 9955089..61019fd 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -1,27 +1,28 @@
import { getLayout } from '@components/Layouts/Layout';
+import PostHeader from '@components/PostHeader/PostHeader';
import PostPreview from '@components/PostPreview/PostPreview';
-import { t } from '@lingui/macro';
-import { NextPageWithLayout } from '@ts/types/app';
-import { TopicPreview, ThematicProps } from '@ts/types/taxonomies';
-import { defaultLocale, loadTranslation } from '@utils/helpers/i18n';
-import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
-import { ParsedUrlQuery } from 'querystring';
-import styles from '@styles/pages/Page.module.scss';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { RelatedTopics, ThematicsList, ToC } from '@components/Widgets';
+import { config } from '@config/website';
import {
getAllThematicsSlug,
getThematicBySlug,
} from '@services/graphql/queries';
-import PostHeader from '@components/PostHeader/PostHeader';
-import { RelatedTopics, ThematicsList, ToC } from '@components/Widgets';
-import { useRef } from 'react';
+import styles from '@styles/pages/Page.module.scss';
+import { NextPageWithLayout } from '@ts/types/app';
import { ArticleMeta } from '@ts/types/articles';
+import { TopicPreview, ThematicProps } from '@ts/types/taxonomies';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
import Head from 'next/head';
-import Sidebar from '@components/Sidebar/Sidebar';
-import { Article, Graph, WebPage } from 'schema-dts';
-import { config } from '@config/website';
import { useRouter } from 'next/router';
+import { ParsedUrlQuery } from 'querystring';
+import { useRef } from 'react';
+import { useIntl } from 'react-intl';
+import { Article, Graph, WebPage } from 'schema-dts';
const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => {
+ const intl = useIntl();
const relatedTopics = useRef<TopicPreview[]>([]);
const router = useRouter();
@@ -118,14 +119,27 @@ const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => {
<div dangerouslySetInnerHTML={{ __html: thematic.content }}></div>
{thematic.posts.length > 0 && (
<section className={styles.section}>
- <h2>{t`All posts in ${thematic.title}`}</h2>
+ <h2>
+ {intl.formatMessage(
+ {
+ defaultMessage: 'All posts in {name}',
+ description: 'ThematicPage: posts list title',
+ },
+ { name: thematic.title }
+ )}
+ </h2>
<ol className={styles.list}>{getPostsList()}</ol>
</section>
)}
</div>
<Sidebar position="right">
<RelatedTopics topics={relatedTopics.current} />
- <ThematicsList title={t`Other thematics`} />
+ <ThematicsList
+ title={intl.formatMessage({
+ defaultMessage: 'Others thematics',
+ description: 'ThematicPage: thematics list widget title',
+ })}
+ />
</Sidebar>
</article>
</>
@@ -142,7 +156,7 @@ export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
const { locale } = context;
- const translation = await loadTranslation(locale || defaultLocale);
+ const translation = await loadTranslation(locale);
const { slug } = context.params as PostParams;
const thematic = await getThematicBySlug(slug);
const breadcrumbTitle = thematic.title;
diff --git a/src/utils/helpers/i18n.ts b/src/utils/helpers/i18n.ts
index 4439906..16c83f4 100644
--- a/src/utils/helpers/i18n.ts
+++ b/src/utils/helpers/i18n.ts
@@ -1,86 +1,49 @@
-import { messages as messagesEn } from '@i18n/en/messages.js';
-import { messages as messagesFr } from '@i18n/fr/messages.js';
-import { i18n, Messages } from '@lingui/core';
-import { en, fr } from 'make-plural/plurals';
+import { config } from '@config/website';
+import { createIntl, createIntlCache, IntlShape } from '@formatjs/intl';
+import { readFile } from 'fs/promises';
+import path from 'path';
-type Catalog = {
- messages: Messages;
-};
+type Messages = { [key: string]: string };
-export const locales = {
- en: 'English',
- fr: 'Français',
-};
-
-export const defaultLocale = 'fr';
+export const defaultLocale = config.locales.defaultLocale;
/**
- * Load the translation with the correct method depending on environment.
+ * Load the translation for the provided locale.
*
- * @param {string} locale - The current locale.
- * @returns {Promise<Messages>} The translated messages.
+ * @param currentLocale - The current locale.
+ * @returns {Promise<Messages>} The translated strings.
*/
-export async function loadTranslation(locale: string): Promise<Messages> {
- let catalog: Catalog;
-
- try {
- if (process.env.NODE_ENV === 'production') {
- catalog = await import(`src/i18n/${locale}/messages`);
- } else {
- catalog = await import(`@lingui/loader!src/i18n/${locale}/messages.po`);
- }
+export async function loadTranslation(
+ currentLocale: string | undefined
+): Promise<Messages> {
+ const locale: string = currentLocale || defaultLocale;
- return catalog.messages;
- } catch (error) {
- console.error('Error while loading translation.');
- throw error;
- }
-}
+ const languagePath = path.join(process.cwd(), `lang/${locale}.json`);
-/**
- * Init lingui.
- *
- * @param {string} locale - The locale to activate.
- * @param {Messages} [messages] - The compiled translation.
- */
-export function initLingui(locale: string, messages?: Messages) {
try {
- i18n.loadLocaleData({
- en: { plurals: en },
- fr: { plurals: fr },
- });
-
- if (messages) {
- i18n.load(locale, messages);
- } else {
- i18n.load({
- en: messagesEn,
- fr: messagesFr,
- });
- }
-
- i18n.activate(locale, Object.keys(locales));
+ const contents = await readFile(languagePath, 'utf8');
+ return JSON.parse(contents);
} catch (error) {
- console.error('Error while Lingui init.');
+ console.error(
+ 'Error: Could not load compiled language files. Please run `yarn run i18n:compile` first."'
+ );
throw error;
}
}
/**
- * Activate the given locale.
+ * Create an Intl object to be used outside components.
*
- * @param {string} locale - The locale to activate.
- * @param {Messages} messages - The compiled translation.
+ * @returns {<Promise<IntlShape<string>>} The Intl object.
*/
-export function activateLocale(currentLocale: string, messages: Messages) {
- const locale: string = Object.keys(locales).includes(currentLocale)
- ? currentLocale
- : defaultLocale;
-
+export async function getIntlInstance(): Promise<IntlShape<string>> {
try {
- initLingui(locale, messages);
+ const cache = createIntlCache();
+ const messages = await loadTranslation(defaultLocale);
+
+ return createIntl({ locale: defaultLocale, messages }, cache);
} catch (error) {
- console.error(`Error while activating ${currentLocale}`);
+ console.error('Error: Could not create an Intl instance.');
throw error;
}
}
diff --git a/src/utils/helpers/prism.ts b/src/utils/helpers/prism.ts
index 86c8f7d..7f10dc9 100644
--- a/src/utils/helpers/prism.ts
+++ b/src/utils/helpers/prism.ts
@@ -1,4 +1,4 @@
-import { t } from '@lingui/macro';
+import { IntlShape } from 'react-intl';
/**
* Check if the current block has a defined language.
@@ -39,13 +39,25 @@ export const addPrismClasses = () => {
/**
* Translate the PrismJS Copy to clipboard button.
*/
-export const translateCopyButton = (locale: string) => {
+export const translateCopyButton = (locale: string, intl: IntlShape) => {
const articles = document.getElementsByTagName('article');
+ const copyText = intl.formatMessage({
+ defaultMessage: 'Copy',
+ description: 'Prism: copy button text (no clicked)',
+ });
+ const copiedText = intl.formatMessage({
+ defaultMessage: 'Copied!',
+ description: 'Prism: copy button text (clicked)',
+ });
+ const errorText = intl.formatMessage({
+ defaultMessage: 'Use Ctrl+c to copy',
+ description: 'Prism: error text',
+ });
Array.from(articles).forEach((article) => {
article.setAttribute('lang', locale);
- article.setAttribute('data-prismjs-copy', t`Copy`);
- article.setAttribute('data-prismjs-copy-success', t`Copied!`);
- article.setAttribute('data-prismjs-copy-error', t`Use Ctrl+c to copy`);
+ article.setAttribute('data-prismjs-copy', copyText);
+ article.setAttribute('data-prismjs-copy-success', copiedText);
+ article.setAttribute('data-prismjs-copy-error', errorText);
});
};
diff --git a/yarn.lock b/yarn.lock
index f8c1741..67f007f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16,6 +16,13 @@
dependencies:
"@babel/highlight" "^7.16.0"
+"@babel/code-frame@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
+ integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
+ dependencies:
+ "@babel/highlight" "^7.16.7"
+
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4":
version "7.16.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
@@ -42,6 +49,27 @@
semver "^6.3.0"
source-map "^0.5.0"
+"@babel/core@^7.10.4":
+ version "7.16.12"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.12.tgz#5edc53c1b71e54881315923ae2aedea2522bb784"
+ integrity sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.16.8"
+ "@babel/helper-compilation-targets" "^7.16.7"
+ "@babel/helper-module-transforms" "^7.16.7"
+ "@babel/helpers" "^7.16.7"
+ "@babel/parser" "^7.16.12"
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.16.10"
+ "@babel/types" "^7.16.8"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.1.2"
+ semver "^6.3.0"
+ source-map "^0.5.0"
+
"@babel/generator@^7.11.6", "@babel/generator@^7.16.0", "@babel/generator@^7.7.2":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2"
@@ -51,6 +79,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.16.8":
+ version "7.16.8"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe"
+ integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==
+ dependencies:
+ "@babel/types" "^7.16.8"
+ jsesc "^2.5.1"
+ source-map "^0.5.0"
+
"@babel/helper-annotate-as-pure@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d"
@@ -76,6 +113,16 @@
browserslist "^4.17.5"
semver "^6.3.0"
+"@babel/helper-compilation-targets@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b"
+ integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==
+ dependencies:
+ "@babel/compat-data" "^7.16.4"
+ "@babel/helper-validator-option" "^7.16.7"
+ browserslist "^4.17.5"
+ semver "^6.3.0"
+
"@babel/helper-create-class-features-plugin@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.0.tgz#090d4d166b342a03a9fec37ef4fd5aeb9c7c6a4b"
@@ -110,6 +157,13 @@
resolve "^1.14.2"
semver "^6.1.2"
+"@babel/helper-environment-visitor@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7"
+ integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-explode-assignable-expression@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778"
@@ -126,6 +180,15 @@
"@babel/template" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/helper-function-name@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f"
+ integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.16.7"
+ "@babel/template" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/helper-get-function-arity@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa"
@@ -133,6 +196,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-get-function-arity@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419"
+ integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-hoist-variables@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a"
@@ -140,6 +210,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-hoist-variables@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
+ integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-member-expression-to-functions@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4"
@@ -154,6 +231,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-module-imports@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
+ integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-module-transforms@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5"
@@ -168,6 +252,20 @@
"@babel/traverse" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/helper-module-transforms@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41"
+ integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-module-imports" "^7.16.7"
+ "@babel/helper-simple-access" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/helper-validator-identifier" "^7.16.7"
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/helper-optimise-call-expression@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338"
@@ -180,6 +278,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
+"@babel/helper-plugin-utils@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
+ integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
+
"@babel/helper-remap-async-to-generator@^7.16.0", "@babel/helper-remap-async-to-generator@^7.16.4":
version "7.16.4"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.4.tgz#5d7902f61349ff6b963e07f06a389ce139fbfe6e"
@@ -206,6 +309,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-simple-access@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7"
+ integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09"
@@ -220,16 +330,33 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-split-export-declaration@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
+ integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
+ dependencies:
+ "@babel/types" "^7.16.7"
+
"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7":
version "7.15.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
+"@babel/helper-validator-identifier@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
+ integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
+
"@babel/helper-validator-option@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
+"@babel/helper-validator-option@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
+ integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
+
"@babel/helper-wrap-function@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz#b3cf318afce774dfe75b86767cd6d68f3482e57c"
@@ -249,6 +376,15 @@
"@babel/traverse" "^7.16.3"
"@babel/types" "^7.16.0"
+"@babel/helpers@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.7.tgz#7e3504d708d50344112767c3542fc5e357fffefc"
+ integrity sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==
+ dependencies:
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a"
@@ -258,11 +394,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.16.7":
+ version "7.16.10"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
+ integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.7.2":
version "7.16.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e"
integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==
+"@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.4", "@babel/parser@^7.16.7":
+ version "7.16.12"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6"
+ integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2":
version "7.16.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183"
@@ -463,6 +613,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-jsx@7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665"
+ integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.16.7"
+
"@babel/plugin-syntax-jsx@7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201"
@@ -980,6 +1137,31 @@
"@babel/parser" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/template@^7.16.7":
+ version "7.16.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
+ integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/parser" "^7.16.7"
+ "@babel/types" "^7.16.7"
+
+"@babel/traverse@7", "@babel/traverse@^7.16.10", "@babel/traverse@^7.16.7":
+ version "7.16.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f"
+ integrity sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.16.8"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.16.7"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.16.10"
+ "@babel/types" "^7.16.8"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2":
version "7.16.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787"
@@ -1011,6 +1193,14 @@
"@babel/helper-validator-identifier" "^7.15.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.12.11", "@babel/types@^7.16.7", "@babel/types@^7.16.8":
+ version "7.16.8"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1"
+ integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1181,6 +1371,109 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@formatjs/cli@^4.8.1":
+ version "4.8.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-4.8.1.tgz#d2fa5308961254f8a575ca976ac279e8e12cf206"
+ integrity sha512-cXA1ir9DEHJu2Ilc964NL5cs5ndwiA8TqSrSNrgjBRuPqQzzo6XE9dgwUk7PQoCA50LRtHpEtfGbf9P7veZqmw==
+ dependencies:
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@formatjs/ts-transformer" "3.9.1"
+ "@types/estree" "^0.0.50"
+ "@types/fs-extra" "^9.0.1"
+ "@types/json-stable-stringify" "^1.0.32"
+ "@types/node" "14"
+ "@vue/compiler-core" "^3.2.23"
+ chalk "^4.0.0"
+ commander "8"
+ fast-glob "^3.2.7"
+ fs-extra "10"
+ json-stable-stringify "^1.0.1"
+ loud-rejection "^2.2.0"
+ tslib "^2.1.0"
+ typescript "^4.5"
+ vue "^3.2.23"
+
+"@formatjs/ecma402-abstract@1.11.2":
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.2.tgz#7f01595e6985a28983aae26bede9b78b273fee3d"
+ integrity sha512-qDgOL0vtfJ51cc0pRbFB/oXc4qDbamG22Z6h/QWy6FBxaQgppiy8JF0iYbmNO35cC8r88bQGsgfd/eM6/eTEQQ==
+ dependencies:
+ "@formatjs/intl-localematcher" "0.2.23"
+ tslib "^2.1.0"
+
+"@formatjs/fast-memoize@1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21"
+ integrity sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==
+ dependencies:
+ tslib "^2.1.0"
+
+"@formatjs/icu-messageformat-parser@2.0.17":
+ version "2.0.17"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.17.tgz#0f817aa06d3b9f23ae0a8bd667b5d7785df5017c"
+ integrity sha512-GO4DzmyiDUyT4p9UxSlOcdnRL1CCt43oHBBGe21s5043UjP6dwMbOotugKs1bRiN+FrNrRUSW+TLdT3+4CBI5A==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/icu-skeleton-parser" "1.3.4"
+ tslib "^2.1.0"
+
+"@formatjs/icu-skeleton-parser@1.3.4":
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.4.tgz#5508ff60cce4eb4698917cb50cb9ff576dde6e5b"
+ integrity sha512-BbKjX3rF3hq2bRjI9NjnSPUrNqI1TwwbMomOBamWfAkpOEf4LYEezPL9tHEds/+sN2/82Z+qEmK7s/l9G2J+qA==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ tslib "^2.1.0"
+
+"@formatjs/intl-displaynames@5.4.1":
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-5.4.1.tgz#09a4b956468a3a1ed332b93f380546ed02dac431"
+ integrity sha512-a95nwJcTM5xRsdwC1Y4msjXPINA6dbDsI043VPlSJRpUtBHWcvdSKvPDZP+KgB9RmR3zYfbJof5BSyPsAHK65w==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/intl-localematcher" "0.2.23"
+ tslib "^2.1.0"
+
+"@formatjs/intl-listformat@6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-6.5.1.tgz#609ebba0cf7301989a261f8c239ec3e46f02ffca"
+ integrity sha512-ijsOM7J7aNnGx+1JYUGWgMAcisnK0CxdlPx7KJpUXKj9Mf2Ph28H2WMTL1h1xv9T7SSvH0Nd6asI0Qw4ffw17w==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/intl-localematcher" "0.2.23"
+ tslib "^2.1.0"
+
+"@formatjs/intl-localematcher@0.2.23":
+ version "0.2.23"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.23.tgz#5a0b1d81df1f392ecf37e556ca7040a7ec9f72e8"
+ integrity sha512-oCe2TOciTtB1bEbJ85EvYrXQxD0epusmVJfJ7AduO0tlbXP42CmDIYIH2CZ+kP2GE+PTLQD1Hbt9kpOpl939MQ==
+ dependencies:
+ tslib "^2.1.0"
+
+"@formatjs/intl@1.18.4":
+ version "1.18.4"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.18.4.tgz#034b99949a1bf18ac7d8d1dff2b5fb3cdbca6c48"
+ integrity sha512-1l93aCrAWRoK8KPD6W5Re9f3XUuNwMuxP12ZFebiG/Wb3eqTASIl9yTUoHwa/FJlNTL1JBRs4PYGCxKeqOod2w==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/fast-memoize" "1.2.1"
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@formatjs/intl-displaynames" "5.4.1"
+ "@formatjs/intl-listformat" "6.5.1"
+ intl-messageformat "9.11.3"
+ tslib "^2.1.0"
+
+"@formatjs/ts-transformer@3.9.1":
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-3.9.1.tgz#92d7d54debf7f427dcc8c9bc57c8813560553708"
+ integrity sha512-FY31pBrqIO8AeL6+vFFCSqBXe4NZyxCfIb1jRColBXiQHbUlmfaoTFu19BXibqbU5CxFd+wG2LhDLZuitGhDBA==
+ dependencies:
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@types/node" "14 || 16 || 17"
+ chalk "^4.0.0"
+ tslib "^2.1.0"
+ typescript "^4.5"
+
"@hapi/accept@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523"
@@ -1843,6 +2136,17 @@
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
+"@types/babel__core@*", "@types/babel__core@^7.1.7":
+ version "7.1.18"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8"
+ integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
version "7.1.17"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64"
@@ -1861,6 +2165,13 @@
dependencies:
"@babel/types" "^7.0.0"
+"@types/babel__helper-plugin-utils@^7.10.0":
+ version "7.10.0"
+ resolved "https://registry.yarnpkg.com/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.0.tgz#dcd2416f9c189d5837ab2a276368cf67134efe78"
+ integrity sha512-60YtHzhQ9HAkToHVV+TB4VLzBn9lrfgrsOjiJMtbv/c1jPdekBxaByd6DMsGBzROXWoIL6U3lEFvvbu69RkUoA==
+ dependencies:
+ "@types/babel__core" "*"
+
"@types/babel__template@*":
version "7.4.1"
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969"
@@ -1883,6 +2194,14 @@
dependencies:
"@types/ms" "*"
+"@types/eslint@8":
+ version "8.4.1"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304"
+ integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
"@types/estree-jsx@^0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-0.0.1.tgz#c36d7a1afeb47a95a8ee0b7bc8bc705db38f919d"
@@ -1900,6 +2219,13 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe"
integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==
+"@types/fs-extra@^9.0.1":
+ version "9.0.13"
+ resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
+ integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==
+ dependencies:
+ "@types/node" "*"
+
"@types/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -1914,6 +2240,14 @@
dependencies:
"@types/unist" "*"
+"@types/hoist-non-react-statics@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1933,6 +2267,16 @@
dependencies:
"@types/istanbul-lib-report" "*"
+"@types/json-schema@*":
+ version "7.0.9"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
+ integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
+
+"@types/json-stable-stringify@^1.0.32":
+ version "1.0.33"
+ resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.33.tgz#099b0712d824d15e2660c20e1c16e6a8381f308c"
+ integrity sha512-qEWiQff6q2tA5gcJGWwzplQcXdJtm+0oy6IHGHzlOf3eFAkGE/FIPXZK9ofWgNSHVp8AFFI33PJJshS0ei3Gvw==
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1970,6 +2314,16 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10"
integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==
+"@types/node@14":
+ version "14.18.9"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.9.tgz#0e5944eefe2b287391279a19b407aa98bd14436d"
+ integrity sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==
+
+"@types/node@14 || 16 || 17":
+ version "17.0.13"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3"
+ integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==
+
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
@@ -1995,19 +2349,19 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
-"@types/react@17.0.37":
- version "17.0.37"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959"
- integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==
+"@types/react@*", "@types/react@16 || 17", "@types/react@>=16":
+ version "17.0.38"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
+ integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
-"@types/react@>=16":
- version "17.0.38"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd"
- integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==
+"@types/react@17.0.37":
+ version "17.0.37"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959"
+ integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@@ -2065,6 +2419,11 @@
"@typescript-eslint/types" "5.6.0"
"@typescript-eslint/visitor-keys" "5.6.0"
+"@typescript-eslint/types@5.10.1":
+ version "5.10.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea"
+ integrity sha512-ZvxQ2QMy49bIIBpTqFiOenucqUyjTQ0WNLhBM6X1fh1NNlYAC6Kxsx8bRTY3jdYsYg44a0Z/uEgQkohbR0H87Q==
+
"@typescript-eslint/types@5.6.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.6.0.tgz#745cb1b59daadcc1f32f7be95f0f68accf38afdd"
@@ -2083,6 +2442,27 @@
semver "^7.3.5"
tsutils "^3.21.0"
+"@typescript-eslint/typescript-estree@^5.9.1":
+ version "5.10.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.1.tgz#b268e67be0553f8790ba3fe87113282977adda15"
+ integrity sha512-PwIGnH7jIueXv4opcwEbVGDATjGPO1dx9RkUl5LlHDSe+FXxPwFL5W/qYd5/NHr7f6lo/vvTrAzd0KlQtRusJQ==
+ dependencies:
+ "@typescript-eslint/types" "5.10.1"
+ "@typescript-eslint/visitor-keys" "5.10.1"
+ debug "^4.3.2"
+ globby "^11.0.4"
+ is-glob "^4.0.3"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
+
+"@typescript-eslint/visitor-keys@5.10.1":
+ version "5.10.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.1.tgz#29102de692f59d7d34ecc457ed59ab5fc558010b"
+ integrity sha512-NjQ0Xinhy9IL979tpoTRuLKxMc0zJC7QVSdeerXs2/QvOy2yRkzX5dRb10X5woNUdJgU8G3nYRDlI33sq1K4YQ==
+ dependencies:
+ "@typescript-eslint/types" "5.10.1"
+ eslint-visitor-keys "^3.0.0"
+
"@typescript-eslint/visitor-keys@5.6.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.6.0.tgz#3e36509e103fe9713d8f035ac977235fd63cb6e6"
@@ -2091,6 +2471,96 @@
"@typescript-eslint/types" "5.6.0"
eslint-visitor-keys "^3.0.0"
+"@vue/compiler-core@3.2.29", "@vue/compiler-core@^3.2.23":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097"
+ integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==
+ dependencies:
+ "@babel/parser" "^7.16.4"
+ "@vue/shared" "3.2.29"
+ estree-walker "^2.0.2"
+ source-map "^0.6.1"
+
+"@vue/compiler-dom@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715"
+ integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==
+ dependencies:
+ "@vue/compiler-core" "3.2.29"
+ "@vue/shared" "3.2.29"
+
+"@vue/compiler-sfc@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead"
+ integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==
+ dependencies:
+ "@babel/parser" "^7.16.4"
+ "@vue/compiler-core" "3.2.29"
+ "@vue/compiler-dom" "3.2.29"
+ "@vue/compiler-ssr" "3.2.29"
+ "@vue/reactivity-transform" "3.2.29"
+ "@vue/shared" "3.2.29"
+ estree-walker "^2.0.2"
+ magic-string "^0.25.7"
+ postcss "^8.1.10"
+ source-map "^0.6.1"
+
+"@vue/compiler-ssr@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e"
+ integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==
+ dependencies:
+ "@vue/compiler-dom" "3.2.29"
+ "@vue/shared" "3.2.29"
+
+"@vue/reactivity-transform@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354"
+ integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==
+ dependencies:
+ "@babel/parser" "^7.16.4"
+ "@vue/compiler-core" "3.2.29"
+ "@vue/shared" "3.2.29"
+ estree-walker "^2.0.2"
+ magic-string "^0.25.7"
+
+"@vue/reactivity@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052"
+ integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g==
+ dependencies:
+ "@vue/shared" "3.2.29"
+
+"@vue/runtime-core@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417"
+ integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig==
+ dependencies:
+ "@vue/reactivity" "3.2.29"
+ "@vue/shared" "3.2.29"
+
+"@vue/runtime-dom@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01"
+ integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==
+ dependencies:
+ "@vue/runtime-core" "3.2.29"
+ "@vue/shared" "3.2.29"
+ csstype "^2.6.8"
+
+"@vue/server-renderer@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102"
+ integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==
+ dependencies:
+ "@vue/compiler-ssr" "3.2.29"
+ "@vue/shared" "3.2.29"
+
+"@vue/shared@3.2.29":
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
+ integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
+
JSONStream@^1.0.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -2266,6 +2736,11 @@ aria-query@^5.0.0:
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+ integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
+
array-ify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
@@ -2391,6 +2866,22 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
+babel-plugin-formatjs@^10.3.17:
+ version "10.3.17"
+ resolved "https://registry.yarnpkg.com/babel-plugin-formatjs/-/babel-plugin-formatjs-10.3.17.tgz#5c93b479cd1dfae2adb0b4d6e7987d2d834abe4d"
+ integrity sha512-VQFCGOrJbZ7kZ6B0KYFlsBeNG3eXH3946e3vWxH5VXLR91slKQlohGPP9F4J63bqIxqcfZWJXBYq4Gb8z3uhPw==
+ dependencies:
+ "@babel/core" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-jsx" "7"
+ "@babel/traverse" "7"
+ "@babel/types" "^7.12.11"
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@formatjs/ts-transformer" "3.9.1"
+ "@types/babel__core" "^7.1.7"
+ "@types/babel__helper-plugin-utils" "^7.10.0"
+ tslib "^2.1.0"
+
babel-plugin-istanbul@^6.0.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
@@ -2962,7 +3453,7 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==
-commander@*, commander@^8.3.0:
+commander@*, commander@8, commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
@@ -3357,11 +3848,23 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
+csstype@^2.6.8:
+ version "2.6.19"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa"
+ integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==
+
csstype@^3.0.2:
version "3.0.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
+ dependencies:
+ array-find-index "^1.0.1"
+
damerau-levenshtein@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d"
@@ -3634,6 +4137,11 @@ emittery@^0.8.1:
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
+emoji-regex@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.0.tgz#96559e19f82231b436403e059571241d627c42b8"
+ integrity sha512-KmJa8l6uHi1HrBI34udwlzZY1jOEuID/ft4d8BSSEdRyap7PwBEt910453PJa5MuGvxkLqlt4Uvhu7tttFHViw==
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -3805,6 +4313,19 @@ eslint-module-utils@^2.7.1:
find-up "^2.1.0"
pkg-dir "^2.0.0"
+eslint-plugin-formatjs@^2.20.5:
+ version "2.20.5"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-2.20.5.tgz#bed01b2586fead3e7b34e40333c49d9f07eb74c3"
+ integrity sha512-VxqoeThPaMMFpAjeGkoGNNFbmUFkLnY1J5m1I2b2yZzjpNF0+FeBykDUxbtGb569TwZRI3qxt5Zn1yXjCU9RjQ==
+ dependencies:
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@formatjs/ts-transformer" "3.9.1"
+ "@types/eslint" "8"
+ "@typescript-eslint/typescript-estree" "^5.9.1"
+ emoji-regex "^10.0.0"
+ tslib "^2.1.0"
+ typescript "^4.5"
+
eslint-plugin-import@^2.25.2:
version "2.25.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766"
@@ -3998,6 +4519,11 @@ estree-util-visit@^1.0.0:
"@types/estree-jsx" "^0.0.1"
"@types/unist" "^2.0.0"
+estree-walker@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
estree-walker@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.1.tgz#c2a9fb4a30232f5039b7c030b37ead691932debd"
@@ -4230,7 +4756,7 @@ fs-access@^1.0.1:
dependencies:
null-check "^1.0.0"
-fs-extra@^10.0.0:
+fs-extra@10, fs-extra@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==
@@ -4588,6 +5114,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -4783,6 +5316,16 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
+intl-messageformat@9.11.3, intl-messageformat@^9.11.3:
+ version "9.11.3"
+ resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.11.3.tgz#e9b26b582891ff0fca327a9ddcb2caf6d26c84e7"
+ integrity sha512-sFOaEw2cytBASTsJkfVod8IJzTx9oOPdU0C7jzprfGATn22FjQGJ60UCyCkKJo6UW+NnpKpwBjO73Pnhvv6HHg==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/fast-memoize" "1.2.1"
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ tslib "^2.1.0"
+
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
@@ -5638,6 +6181,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=
+ dependencies:
+ jsonify "~0.0.0"
+
json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -5666,6 +6216,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+ integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
+
jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@@ -5899,6 +6454,14 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+loud-rejection@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-2.2.0.tgz#4255eb6e9c74045b0edc021fa7397ab655a8517c"
+ integrity sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ==
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.2"
+
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -5911,6 +6474,13 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
+magic-string@^0.25.7:
+ version "0.25.7"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
+ integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
+ dependencies:
+ sourcemap-codec "^1.4.4"
+
make-dir@^3.0.0, make-dir@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -7127,6 +7697,15 @@ postcss@8.2.15:
nanoid "^3.1.23"
source-map "^0.6.1"
+postcss@^8.1.10:
+ version "8.4.5"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
+ integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==
+ dependencies:
+ nanoid "^3.1.30"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.1"
+
postcss@^8.3.11:
version "8.4.4"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.4.tgz#d53d4ec6a75fd62557a66bb41978bf47ff0c2869"
@@ -7313,12 +7892,28 @@ react-dom@17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
+react-intl@^5.24.4:
+ version "5.24.4"
+ resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.24.4.tgz#1c9dbc5b4e33b068e8c73a2b919af6e681fee5b2"
+ integrity sha512-c3OaJNZUt8CqqjVge+YPof76xRp6HrxmfKtiEB3LOBu466ISliGLPiy3goOdNs9Vj/0+jGagcAk8jqh/pAscAw==
+ dependencies:
+ "@formatjs/ecma402-abstract" "1.11.2"
+ "@formatjs/icu-messageformat-parser" "2.0.17"
+ "@formatjs/intl" "1.18.4"
+ "@formatjs/intl-displaynames" "5.4.1"
+ "@formatjs/intl-listformat" "6.5.1"
+ "@types/hoist-non-react-statics" "^3.3.1"
+ "@types/react" "16 || 17"
+ hoist-non-react-statics "^3.3.2"
+ intl-messageformat "9.11.3"
+ tslib "^2.1.0"
+
react-is@17.0.2, "react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-is@^16.8.1:
+react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7839,6 +8434,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+sourcemap-codec@^1.4.4:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
space-separated-tokens@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b"
@@ -8449,7 +9049,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2:
+tslib@^2, tslib@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@@ -8537,6 +9137,11 @@ typescript@4.5.3, typescript@^4.4.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.3.tgz#afaa858e68c7103317d89eb90c5d8906268d353c"
integrity sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ==
+typescript@^4.5:
+ version "4.5.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
+ integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
+
uglify-js@^3.1.4:
version "3.14.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.4.tgz#68756f17d1b90b9d289341736cb9a567d6882f90"
@@ -8768,6 +9373,17 @@ vm-browserify@1.1.2:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+vue@^3.2.23:
+ version "3.2.29"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a"
+ integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==
+ dependencies:
+ "@vue/compiler-dom" "3.2.29"
+ "@vue/compiler-sfc" "3.2.29"
+ "@vue/runtime-dom" "3.2.29"
+ "@vue/server-renderer" "3.2.29"
+ "@vue/shared" "3.2.29"
+
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"