From 779de85ff45cc8cf7ee35183f88c3efcfb899942 Mon Sep 17 00:00:00 2001 From: Brandon Tabaska Date: Thu, 8 Aug 2024 11:51:21 -0400 Subject: [PATCH] [Issue #167] Fix search page string translation (#169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #167 ### Time to review: __5 mins__ ## Changes proposed Updated most search strings on Search page to use correct next-intl translation components Added strings to Messages file ## Context for reviewers Updated the unit tests as well because they were not inheriting context due to improper import path from non-global context source ## Additional information Screenshot 2024-08-07 at 1 36 18 PM --- frontend/src/app/[locale]/search/page.tsx | 12 ++-- frontend/src/components/search/SearchBar.tsx | 15 ++-- .../components/search/SearchCallToAction.tsx | 11 +-- .../SearchFilterToggleAll.tsx | 7 +- .../search/SearchOpportunityStatus.tsx | 37 +++++++--- .../components/search/SearchResultsHeader.tsx | 6 +- .../search/SearchResultsListFetch.tsx | 19 ++--- .../search/SearchResultsListItem.tsx | 21 +++--- .../src/components/search/SearchSortBy.tsx | 42 +++++++---- frontend/src/i18n/messages/en/index.ts | 72 +++++++++++++++++++ .../components/search/SearchBar.test.tsx | 4 +- .../SearchFilterAccordion.test.tsx | 3 +- .../SearchFilterCheckbox.test.tsx | 3 +- .../SearchFilterSection.test.tsx | 3 +- .../SectionLinkCount.test.tsx | 2 +- .../SectionLinkLabel.test.tsx | 2 +- .../SearchFilterToggleAll.test.tsx | 3 +- .../search/SearchOpportunityStatus.test.tsx | 3 +- .../search/SearchPagination.test.tsx | 2 +- .../search/SearchResultsHeader.test.tsx | 2 +- .../search/SearchResultsListItem.test.tsx | 3 +- .../components/search/SearchSortBy.test.tsx | 4 +- 22 files changed, 201 insertions(+), 75 deletions(-) diff --git a/frontend/src/app/[locale]/search/page.tsx b/frontend/src/app/[locale]/search/page.tsx index c91209ae3..d8e15d44f 100644 --- a/frontend/src/app/[locale]/search/page.tsx +++ b/frontend/src/app/[locale]/search/page.tsx @@ -49,7 +49,7 @@ interface searchParamsTypes { function Search({ searchParams }: { searchParams: searchParamsTypes }) { unstable_setRequestLocale("en"); - const t = useTranslations("Process"); + const t = useTranslations("Search"); const convertedSearchParams = convertSearchParamsToProperTypes(searchParams); const { agency, @@ -71,7 +71,7 @@ function Search({ searchParams }: { searchParams: searchParamsTypes }) { return ( <> - + @@ -85,25 +85,25 @@ function Search({ searchParams }: { searchParams: searchParamsTypes }) { diff --git a/frontend/src/components/search/SearchBar.tsx b/frontend/src/components/search/SearchBar.tsx index 331c2819a..aad6e6c45 100644 --- a/frontend/src/components/search/SearchBar.tsx +++ b/frontend/src/components/search/SearchBar.tsx @@ -3,6 +3,7 @@ import { Icon } from "@trussworks/react-uswds"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; +import { useTranslations } from "next-intl"; interface SearchBarProps { query: string | null | undefined; @@ -16,16 +17,20 @@ export default function SearchBar({ query }: SearchBarProps) { updateQueryParams("", "query", queryTerm, false); }; + const t = useTranslations("Search"); + return (
@@ -36,7 +39,7 @@ const SearchFilterToggleAll: React.FC = ({ }} disabled={isNoneSelected} > - Clear All + {t("filterToggleAll.clear")}
diff --git a/frontend/src/components/search/SearchOpportunityStatus.tsx b/frontend/src/components/search/SearchOpportunityStatus.tsx index 752647b9d..c9bc65c8b 100644 --- a/frontend/src/components/search/SearchOpportunityStatus.tsx +++ b/frontend/src/components/search/SearchOpportunityStatus.tsx @@ -3,6 +3,7 @@ import { Checkbox } from "@trussworks/react-uswds"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; +import { useTranslations } from "next-intl"; interface StatusOption { id: string; @@ -14,13 +15,6 @@ interface SearchOpportunityStatusProps { query: Set; } -const statusOptions: StatusOption[] = [ - { id: "status-forecasted", label: "Forecasted", value: "forecasted" }, - { id: "status-posted", label: "Posted", value: "posted" }, - { id: "status-closed", label: "Closed", value: "closed" }, - { id: "status-archived", label: "Archived", value: "archived" }, -]; - export default function SearchOpportunityStatus({ query, }: SearchOpportunityStatusProps) { @@ -33,9 +27,36 @@ export default function SearchOpportunityStatus({ updateQueryParams(updated, "status", queryTerm); }; + const t = useTranslations("Search"); + + const statusOptions: StatusOption[] = [ + { + id: "status-forecasted", + label: t("opportunityStatus.label.forecasted"), + value: "forecasted", + }, + { + id: "status-posted", + label: t("opportunityStatus.label.posted"), + value: "posted", + }, + { + id: "status-closed", + label: t("opportunityStatus.label.closed"), + value: "closed", + }, + { + id: "status-archived", + label: t("opportunityStatus.label.archived"), + value: "archived", + }, + ]; + return ( <> -

Opportunity status

+

+ {t("opportunityStatus.title")} +

{statusOptions.map((option) => { return ( diff --git a/frontend/src/components/search/SearchResultsHeader.tsx b/frontend/src/components/search/SearchResultsHeader.tsx index 72296d0bf..24e74b27a 100644 --- a/frontend/src/components/search/SearchResultsHeader.tsx +++ b/frontend/src/components/search/SearchResultsHeader.tsx @@ -2,6 +2,7 @@ import SearchSortyBy from "./SearchSortBy"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; +import { useTranslations } from "next-intl"; export default function SearchResultsHeader({ sortby, @@ -23,10 +24,13 @@ export default function SearchResultsHeader({ "tablet-lg:margin-bottom-0", ]; if (loading) gridRowClasses.push("opacity-50"); + + const t = useTranslations("Search"); + return (

- {total && <>{total} Opportunities} + {t("resultsHeader.message", { count: total })}

; @@ -21,12 +23,11 @@ export default async function SearchResultsListFetch({ if (searchResults.data.length === 0) { return (
-

Your search did not return any results.

+

{t("resultsListFetch.title")}

    -
  • {"Check any terms you've entered for typos"}
  • -
  • Try different keywords
  • -
  • {"Make sure you've selected the right statuses"}
  • -
  • Try resetting filters or selecting fewer options
  • + {t.rich("resultsListFetch.body", { + li: (chunks) =>
  • {chunks}
  • , + })}
); @@ -35,13 +36,7 @@ export default async function SearchResultsListFetch({ return (
    {/* TODO #1485: show proper USWDS error */} - {maxPaginationError && ( -

    - { - "You''re trying to access opportunity results that are beyond the last page of data." - } -

    - )} + {maxPaginationError &&

    {t("resultsListFetch.paginationError")}

    } {searchResults.data.map((opportunity) => (
  • diff --git a/frontend/src/components/search/SearchResultsListItem.tsx b/frontend/src/components/search/SearchResultsListItem.tsx index caa71630d..301a6a68c 100644 --- a/frontend/src/components/search/SearchResultsListItem.tsx +++ b/frontend/src/components/search/SearchResultsListItem.tsx @@ -2,6 +2,7 @@ import { AgencyNamyLookup } from "src/utils/search/generateAgencyNameLookup"; import { formatDate } from "src/utils/dateUtil"; import { Opportunity } from "src/types/search/searchResponseTypes"; +import { useTranslations } from "next-intl"; interface SearchResultsListItemProps { opportunity: Opportunity; @@ -19,6 +20,8 @@ export default function SearchResultsListItem({ ? "https://grants.gov" : "https://test.grants.gov"; + const t = useTranslations("Search"); + const metadataBorderClasses = ` display-block tablet:display-inline-block @@ -56,7 +59,7 @@ export default function SearchResultsListItem({
    {opportunity.opportunity_status === "archived" && ( - Archived:{" "} + {t("resultsListItem.status.archived")} {opportunity?.summary?.archive_date ? formatDate(opportunity?.summary?.archive_date) : "--"} @@ -66,7 +69,7 @@ export default function SearchResultsListItem({ opportunity?.opportunity_status === "closed") && opportunity?.summary?.close_date && ( - Closed:{" "} + {t("resultsListItem.status.closed")} {opportunity?.summary?.close_date ? formatDate(opportunity?.summary?.close_date) : "--"} @@ -75,7 +78,7 @@ export default function SearchResultsListItem({ {opportunity?.opportunity_status === "posted" && ( - Closing:{" "} + {t("resultsListItem.status.posted")} {opportunity?.summary?.close_date ? formatDate(opportunity?.summary?.close_date) @@ -87,12 +90,12 @@ export default function SearchResultsListItem({ {opportunity?.opportunity_status === "forecasted" && ( - Forecasted + {t("resultsListItem.status.forecasted")} )} - Posted:{" "} + {t("resultsListItem.summary.posted")} {opportunity?.summary?.post_date ? formatDate(opportunity?.summary?.post_date) : "--"} @@ -100,7 +103,7 @@ export default function SearchResultsListItem({
    - Agency:{" "} + {t("resultsListItem.summary.agency")} {opportunity?.summary?.agency_name && opportunity?.summary?.agency_code && agencyNameLookup @@ -109,7 +112,7 @@ export default function SearchResultsListItem({ : "--"} - Opportunity Number:{" "} + {t("resultsListItem.opportunity_number")} {opportunity?.opportunity_number}
    @@ -121,7 +124,7 @@ export default function SearchResultsListItem({ - Award Ceiling:{" "} + {t("resultsListItem.award_ceiling")} ${opportunity?.summary?.award_ceiling?.toLocaleString() || "--"} @@ -129,7 +132,7 @@ export default function SearchResultsListItem({ - Floor: $ + {t("resultsListItem.floor")} {opportunity?.summary?.award_floor?.toLocaleString() || "--"}
diff --git a/frontend/src/components/search/SearchSortBy.tsx b/frontend/src/components/search/SearchSortBy.tsx index da13acd66..94d1963ab 100644 --- a/frontend/src/components/search/SearchSortBy.tsx +++ b/frontend/src/components/search/SearchSortBy.tsx @@ -3,25 +3,13 @@ import { Select } from "@trussworks/react-uswds"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; +import { useTranslations } from "next-intl"; type SortOption = { label: string; value: string; }; -const SORT_OPTIONS: SortOption[] = [ - { label: "Posted Date (newest)", value: "postedDateDesc" }, - { label: "Posted Date (oldest)", value: "postedDateAsc" }, - { label: "Close Date (newest)", value: "closeDateDesc" }, - { label: "Close Date (oldest)", value: "closeDateAsc" }, - { label: "Opportunity Title (A to Z)", value: "opportunityTitleAsc" }, - { label: "Opportunity Title (Z to A)", value: "opportunityTitleDesc" }, - { label: "Agency (A to Z)", value: "agencyAsc" }, - { label: "Agency (Z to A)", value: "agencyDesc" }, - { label: "Opportunity Number (descending)", value: "opportunityNumberDesc" }, - { label: "Opportunity Number (ascending)", value: "opportunityNumberAsc" }, -]; - interface SearchSortByProps { queryTerm: string | null | undefined; sortby: string | null; @@ -35,6 +23,32 @@ export default function SearchSortBy({ }: SearchSortByProps) { const { updateQueryParams } = useSearchParamUpdater(); const { updateTotalResults } = useContext(QueryContext); + const t = useTranslations("Search"); + + const SORT_OPTIONS: SortOption[] = [ + { label: t("sortBy.options.posted_date_desc"), value: "postedDateDesc" }, + { label: t("sortBy.options.posted_date_asc"), value: "postedDateAsc" }, + { label: t("sortBy.options.close_date_desc"), value: "closeDateDesc" }, + { label: t("sortBy.options.close_date_asc"), value: "closeDateAsc" }, + { + label: t("sortBy.options.opportunity_title_asc"), + value: "opportunityTitleAsc", + }, + { + label: t("sortBy.options.opportunity_title_desc"), + value: "opportunityTitleDesc", + }, + { label: t("sortBy.options.agency_asc"), value: "agencyAsc" }, + { label: t("sortBy.options.agency_desc"), value: "agencyDesc" }, + { + label: t("sortBy.options.opportunity_number_desc"), + value: "opportunityNumberDesc", + }, + { + label: t("sortBy.options.opportunity_number_asc"), + value: "opportunityNumberAsc", + }, + ]; const handleChange = (event: React.ChangeEvent) => { const newValue = event.target.value; @@ -45,7 +59,7 @@ export default function SearchSortBy({ return (