From db98f401fd94e72ef0720dbfdeabb1fcd6899ac9 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Wed, 11 Sep 2024 15:44:57 +0200 Subject: [PATCH] feat(client): add intl --- project/client/package-lock.json | 86 +++++++++++++++++++ project/client/package.json | 1 + .../src/components/results/debug/index.tsx | 19 ++-- .../components/results/highlights/index.tsx | 5 +- .../client/src/components/results/index.tsx | 10 ++- project/client/src/config/messages.ts | 11 +++ project/client/src/locales/en.json | 7 ++ project/client/src/pages/home.tsx | 17 +++- 8 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 project/client/src/config/messages.ts create mode 100644 project/client/src/locales/en.json diff --git a/project/client/package-lock.json b/project/client/package-lock.json index 939ee1f..eacdc8e 100644 --- a/project/client/package-lock.json +++ b/project/client/package-lock.json @@ -19,6 +19,7 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intl": "^6.6.8", "react-router-dom": "^6.11.1", "typescript": "^5.4.5" }, @@ -881,6 +882,48 @@ "tslib": "^2.4.0" } }, + "node_modules/@formatjs/intl": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.4.tgz", + "integrity": "sha512-56483O+HVcL0c7VucAS2tyH020mt9XTozZO67cwtGg0a7KWDukS/FzW3OnvaHmTHDuYsoPIzO+ZHVfU6fT/bJw==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "intl-messageformat": "10.5.14", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@formatjs/intl-displaynames": { + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.8.tgz", + "integrity": "sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-listformat": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz", + "integrity": "sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, "node_modules/@formatjs/intl-localematcher": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", @@ -2580,6 +2623,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4391,6 +4443,14 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5495,6 +5555,32 @@ "react": "^18.2.0" } }, + "node_modules/react-intl": { + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", + "integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl": "2.10.4", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "16 || 17 || 18", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "10.5.14", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "react": "^16.6.0 || 17 || 18", + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/project/client/package.json b/project/client/package.json index 0198f17..c55f902 100644 --- a/project/client/package.json +++ b/project/client/package.json @@ -22,6 +22,7 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intl": "^6.6.8", "react-router-dom": "^6.11.1", "typescript": "^5.4.5" }, diff --git a/project/client/src/components/results/debug/index.tsx b/project/client/src/components/results/debug/index.tsx index f180ee6..bdd1d50 100644 --- a/project/client/src/components/results/debug/index.tsx +++ b/project/client/src/components/results/debug/index.tsx @@ -1,34 +1,39 @@ import { Container, Badge, BadgeGroup, Accordion } from "@dataesr/dsfr-plus" +import { useIntl } from "react-intl" import { MatchDebug } from "../../../types" type ResultsDebugArgs = { resultsDebug: MatchDebug } export default function ResultsDebug({ resultsDebug }: ResultsDebugArgs) { + const intl = useIntl() if (!resultsDebug) return null const criterionMatches = (criterion: string) => resultsDebug.criterion?.[criterion] ?? 0 return ( - + {resultsDebug.strategies.map((strategy, index) => ( - {`Matching ${ + {`Matching ${ strategy.equivalent_strategies.length - } strategies : ${strategy.possibilities} ${strategy.possibilities == 1 ? "possibility" : "possibilities"}`} + } strategies : ${intl.formatMessage({ id: "possibility.count" }, { count: strategy.possibilities })}`} {strategy.equivalent_strategies.map((equivalent) => ( {equivalent.criteria.map((criterion) => { const matches: number = criterionMatches(criterion) return ( - {`${criterion}: ${matches} match${ - matches == 1 ? "" : "es" - }`} + {`${criterion}: ${intl.formatMessage( + { id: "match.count" }, + { count: matches } + )}`} ) })} {equivalent.matches > 0 && ( - {`${equivalent.matches} match${equivalent.matches == 1 ? "" : "es"}`} + + {intl.formatMessage({ id: "match.count" }, { count: equivalent.matches })} + )} diff --git a/project/client/src/components/results/highlights/index.tsx b/project/client/src/components/results/highlights/index.tsx index 7b66133..69a2f2f 100644 --- a/project/client/src/components/results/highlights/index.tsx +++ b/project/client/src/components/results/highlights/index.tsx @@ -24,10 +24,13 @@ export default function ResultHighlights({ resultHighlights, setTitle }: ResultH {Object.entries(resultHighlights.criterion).map(([criterion, highlights], groupIndex) => ( - {criterion} + + {criterion} + {highlights.map((highlight, badgeIndex) => ( onEnter(getHighlightedQuery(highlight, currentQuery))} onMouseLeave={() => onLeave()} > diff --git a/project/client/src/components/results/index.tsx b/project/client/src/components/results/index.tsx index ddc4db9..902fc94 100644 --- a/project/client/src/components/results/index.tsx +++ b/project/client/src/components/results/index.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react" +import { useIntl } from "react-intl" import { Container, Text, Badge } from "@dataesr/dsfr-plus" import { MatchResults } from "../../types" import Error from "../error" @@ -10,6 +11,7 @@ import Info from "../info" import useMatch from "../../hooks/useMatch" export default function Results() { + const intl = useIntl() const { currentQuery, currentMatcher, currentYear } = useUrl() const { data, isFetching, error } = useMatch(currentQuery, currentMatcher, currentYear) const [currentTitle, setTitle] = useState(currentQuery) @@ -17,8 +19,8 @@ export default function Results() { useEffect(() => setTitle(currentQuery), [currentQuery]) if (currentQuery === null && currentMatcher === null) return null - if (currentQuery === "") return - if (!currentMatcher) return + if (currentQuery === "") return + if (!currentMatcher) return if (isFetching) return @@ -36,7 +38,7 @@ export default function Results() { {currentTitle} - {`${currentMatcher} : 0 matches`} + {`${currentMatcher} : ${intl.formatMessage({ id: "match.count" }, { count: 0 })}`} @@ -48,7 +50,7 @@ export default function Results() { {currentTitle} - {`${matchIds.length} match${matchIds.length > 1 ? "es" : ""}`} + {intl.formatMessage({ id: "match.count" }, { count: matchIds.length })} {matchIds.map((id, index) => { diff --git a/project/client/src/config/messages.ts b/project/client/src/config/messages.ts new file mode 100644 index 0000000..71d901a --- /dev/null +++ b/project/client/src/config/messages.ts @@ -0,0 +1,11 @@ +const modules = import.meta.glob("../locales/*.json", { + eager: true, + import: "default", +}) +export const messages = Object.keys(modules).reduce((acc, key) => { + const locale = key.match(/\.\/locales\/(.+)\.json$/)?.[1] + if (locale) { + return { ...acc, [locale]: modules[key] } + } + return acc +}, {}) diff --git a/project/client/src/locales/en.json b/project/client/src/locales/en.json new file mode 100644 index 0000000..09cc2ed --- /dev/null +++ b/project/client/src/locales/en.json @@ -0,0 +1,7 @@ +{ + "info.missing.query": "Please enter a text affiliation", + "info.missing.matcher": "Please select a matcher", + "debug.accordion.title": "Matching details", + "match.count": "{count, plural, =0 {# matches} one {# match} other {# matches}}", + "possibility.count": "{count, plural, =0 {# possibilities} one {# possibility} other {# possibilities}}" +} \ No newline at end of file diff --git a/project/client/src/pages/home.tsx b/project/client/src/pages/home.tsx index e3d7ce1..0146de4 100644 --- a/project/client/src/pages/home.tsx +++ b/project/client/src/pages/home.tsx @@ -1,8 +1,10 @@ +import { createIntl, RawIntlProvider } from "react-intl" import { Container } from "@dataesr/dsfr-plus" +import { messages } from "../config/messages" import Input from "../components/input" import Results from "../components/results" -export default function Home() { +function HomePage() { return ( @@ -10,3 +12,16 @@ export default function Home() { ) } + +export default function Home() { + const intl = createIntl({ + locale: "en", + messages: messages["en"], + }) + + return ( + + + + ) +}