Skip to content

Commit

Permalink
feat: Added scripts and marked strings for i18n.
Browse files Browse the repository at this point in the history
  • Loading branch information
saleem-latif committed May 17, 2024
1 parent f17405e commit cb2edf1
Show file tree
Hide file tree
Showing 21 changed files with 395 additions and 94 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ lerna-debug.log
coverage/
dist/
packages/*/package-lock.json

# Transifex
i18n/transifex_input.json
temp
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./i18n
transifex_input = $(i18n)/transifex_input.json

# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-formatjs

i18n.extract:
# Pulling display strings from .jsx files into .json files...
rm -rf $(transifex_temp)
npm run-script i18n_extract

i18n.concat:
# Gathering JSON messages into one file...
$(transifex_utils) $(transifex_temp) $(transifex_input)

extract_translations: | requirements i18n.extract i18n.concat

.PHONY: requirements
requirements: ## install ci requirements
npm ci
1 change: 1 addition & 0 deletions i18n/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty file to preserve directory structure
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"lint:fix": "npm run lint:fix --workspaces",
"bootstrap": "npm install conventional-changelog-conventionalcommits",
"changed": "lerna changed",
"lerna:version": "npx lerna@6 version --conventional-commits --create-release github --no-push"
"lerna:version": "npx lerna@6 version --conventional-commits --create-release github --no-push",
"i18n_extract": "fedx-scripts formatjs extract packages/**/**/*.{js,jsx,ts,tsx}"
},
"devDependencies": {
"@commitlint/config-conventional": "17.6.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/catalog-search/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@edx/frontend-enterprise-catalog-search",
"version": "10.2.0",
"version": "11.0.0",
"description": "Components related to Enterprise catalog search.",
"repository": {
"type": "git",
Expand Down
7 changes: 6 additions & 1 deletion packages/catalog-search/src/ClearCurrentRefinements.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Button } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { SearchContext } from './SearchContext';
import { clearRefinementsAction } from './data/actions';

Expand Down Expand Up @@ -28,7 +29,11 @@ const ClearCurrentRefinements = ({ className, variant, ...props }) => {
onClick={handleClearAllRefinementsClick}
{...props}
>
{CLEAR_ALL_TEXT}
<FormattedMessage
id="search.facetFilters.clearAll.button"
defaultMessage="clear all"
description="Button text to clear all filters"
/>
</Button>
) }
</span>
Expand Down
52 changes: 47 additions & 5 deletions packages/catalog-search/src/CurrentRefinements.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import classNames from 'classnames';
import { Badge, Button } from '@openedx/paragon';
import { CloseSmall } from '@openedx/paragon/icons';
import { connectCurrentRefinements } from 'react-instantsearch-dom';
import { FormattedMessage, defineMessages, useIntl } from '@edx/frontend-platform/i18n';

import ClearCurrentRefinements from './ClearCurrentRefinements';

Expand All @@ -14,13 +15,33 @@ import {
NUM_CURRENT_REFINEMENTS_TO_DISPLAY,
STYLE_VARIANTS,
LEARNING_TYPE_PATHWAY,
LEARNING_TYPE_COURSE,
LEARNING_TYPE_PROGRAM,
} from './data/constants';
import {
useActiveRefinementsAsFlatArray,
} from './data/hooks';
import { SearchContext } from './SearchContext';
import { removeFromRefinementArray, deleteRefinementAction } from './data/actions';

const messages = defineMessages({
[LEARNING_TYPE_COURSE]: {
id: 'search.facetFilters.filterTitle.course',
defaultMessage: 'Course',
description: 'Title for the course filter.',
},
[LEARNING_TYPE_PROGRAM]: {
id: 'search.facetFilters.filterTitle.program',
defaultMessage: 'Program',
description: 'Title for the program filter.',
},
[LEARNING_TYPE_PATHWAY]: {
id: 'search.facetFilters.filterTitle.pathway',
defaultMessage: 'Pathway',
description: 'Title for the pathway filter.',
},
});

export const CurrentRefinementsBase = ({ items, variant }) => {
if (!items || !items.length) {
return null;
Expand All @@ -29,6 +50,7 @@ export const CurrentRefinementsBase = ({ items, variant }) => {
const [showAllRefinements, setShowAllRefinements] = useState(false);
const { refinements, dispatch } = useContext(SearchContext);
const activeRefinementsAsFlatArray = useActiveRefinementsAsFlatArray(items);
const intl = useIntl();

/**
* Determines the correct number of active refinements to show at any
Expand Down Expand Up @@ -83,10 +105,19 @@ export const CurrentRefinementsBase = ({ items, variant }) => {
variant="light"
onClick={() => handleRefinementBadgeClick(item)}
>
{/* Temporary fix : can be removed when learnerpathway content type is changed to pathways */}
<span className="mr-2">{item.label === LEARNING_TYPE_PATHWAY ? 'Pathway' : item.label}</span>
<span className="mr-2">
{messages[item.label] ? intl.formatMessage(messages[item.label]) : item.label}
</span>

<CloseSmall />
<span className="sr-only">Remove the filter {item.label}</span>
<span className="sr-only">
<FormattedMessage
id="search.facetFilters.removeFilter.button"
defaultMessage="Remove the filter {filterTitle}"
description="Button text to remove a filter from the search results"
values={{ filterTitle: item.label }}
/>
</span>
</Badge>
</li>
))}
Expand All @@ -98,7 +129,14 @@ export const CurrentRefinementsBase = ({ items, variant }) => {
onClick={() => setShowAllRefinements(true)}
>
+{activeRefinementsAsFlatArray.length - NUM_CURRENT_REFINEMENTS_TO_DISPLAY}
<span className="sr-only">Show all {activeRefinementsAsFlatArray.length} filters</span>
<span className="sr-only">
<FormattedMessage
id="search.facetFilters.showAll.button"
defaultMessage="Show all {activeRefinementsCount} filters"
description="Button text to show all filters"
values={{ activeRefinementsCount: activeRefinementsAsFlatArray.length }}
/>
</span>
</Badge>
</li>
)}
Expand All @@ -113,7 +151,11 @@ export const CurrentRefinementsBase = ({ items, variant }) => {
variant="link"
size="inline"
>
show less
<FormattedMessage
id="search.facetFilters.showLess.button"
defaultMessage="show less"
description="Button text to show less filters"
/>
</Button>
</li>
)}
Expand Down
32 changes: 27 additions & 5 deletions packages/catalog-search/src/LearningTypeRadioFacet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Dropdown, Input } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { SearchContext } from './SearchContext';
import {
setRefinementAction,
Expand All @@ -11,6 +12,7 @@ import { LEARNING_TYPE_COURSE, LEARNING_TYPE_PROGRAM, LEARNING_TYPE_PATHWAY } fr

const LearningTypeRadioFacet = ({ enablePathways }) => {
const { refinements, dispatch } = useContext(SearchContext);

// only bold the dropdown title if the learning type is Course or Program
const typeCourseSelected = refinements.content_type && refinements.content_type.includes(LEARNING_TYPE_COURSE);
const typeProgramSelected = refinements.content_type && refinements.content_type.includes(LEARNING_TYPE_PROGRAM);
Expand All @@ -33,7 +35,11 @@ const LearningTypeRadioFacet = ({ enablePathways }) => {
variant="inverse-primary"
className={classNames({ 'font-weight-bold': boldTitle })}
>
Learning Type
<FormattedMessage
id="search.facetFilters.learningType.title"
defaultMessage="Learning Type"
description="Title for the learning type facet filter"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item as="label" className="mb-0 py-3 d-flex align-items-center">
Expand All @@ -45,7 +51,11 @@ const LearningTypeRadioFacet = ({ enablePathways }) => {
data-testid="learning-type-any"
/>
<span className={classNames('facet-item-label', {})}>
Any
<FormattedMessage
id="search.facetFilters.learningType.any"
defaultMessage="Any"
description="Title for the learning type facet filter to return all types of learning content"
/>
</span>
</Dropdown.Item>
<Dropdown.Item as="label" className="mb-0 py-3 d-flex align-items-center">
Expand All @@ -57,7 +67,11 @@ const LearningTypeRadioFacet = ({ enablePathways }) => {
data-testid="learning-type-courses"
/>
<span className={classNames('facet-item-label', { 'is-refined': typeCourseSelected })}>
Courses
<FormattedMessage
id="search.facetFilters.learningType.courses"
defaultMessage="Courses"
description="Title for the learning type facet filter to return courses only"
/>
</span>
</Dropdown.Item>
<Dropdown.Item as="label" className="mb-0 py-3 d-flex align-items-center">
Expand All @@ -69,7 +83,11 @@ const LearningTypeRadioFacet = ({ enablePathways }) => {
data-testid="learning-type-programs"
/>
<span className={classNames('facet-item-label', { 'is-refined': typeProgramSelected })}>
Programs
<FormattedMessage
id="search.facetFilters.learningType.programs"
defaultMessage="Programs"
description="Title for the learning type facet filter to return programs only"
/>
</span>
</Dropdown.Item>
{
Expand All @@ -85,7 +103,11 @@ const LearningTypeRadioFacet = ({ enablePathways }) => {
data-testid="learning-type-pathways"
/>
<span className={classNames('facet-item-label', { 'is-refined': typePathwaySelected })}>
Pathways
<FormattedMessage
id="search.facetFilters.learningType.pathways"
defaultMessage="Pathways"
description="Title for the learning type facet filter to return pathways only"
/>
</span>
</Dropdown.Item>
)
Expand Down
14 changes: 12 additions & 2 deletions packages/catalog-search/src/SearchBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import classNames from 'classnames';
import { SearchField } from '@openedx/paragon';
import debounce from 'lodash.debounce';
import { connectSearchBox } from 'react-instantsearch-dom';
import { useIntl, defineMessages } from '@edx/frontend-platform/i18n';

import { sendTrackEvent } from '@edx/frontend-platform/analytics';

Expand All @@ -26,7 +27,15 @@ import {
} from './data/constants';
import SearchSuggestions from './SearchSuggestions';

export const searchText = 'Search courses';
const messages = defineMessages({
searchCoursesText: {
id: 'header.search.input.box.placeholder',
description: 'Placeholder text for the search input box',
defaultMessage: 'Search courses',
},
});

export const searchText = messages.searchCoursesText.defaultMessage;
// this prefix will be combined with one of the SearchBox props to create a full tracking event name
// only if event name prop is provided by user. In the absence of the tracking name prop,
// no tracking event will be sent.
Expand All @@ -53,6 +62,7 @@ export const SearchBoxBase = ({
const [showSuggestions, setShowSuggestions] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [preQueryHits, setPreQueryHits] = useState([]);
const intl = useIntl();

/**
* Handles when a search is submitted by adding the user's search
Expand Down Expand Up @@ -155,7 +165,7 @@ export const SearchBoxBase = ({
{!hideTitle && (
/* eslint-disable-next-line jsx-a11y/label-has-associated-control */
<label id="search-input-box" className="fe__searchfield-input-box text-brand-primary">
{headerTitle || searchText}
{headerTitle || intl.formatMessage(messages.searchCoursesText)}
</label>
)}
<SearchField.Advanced
Expand Down
15 changes: 10 additions & 5 deletions packages/catalog-search/src/SearchContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import React, {
import PropTypes from 'prop-types';
import { useLocation, useNavigate } from 'react-router-dom';
import { useIsFirstRender } from '@edx/frontend-enterprise-utils';
import { useIntl } from '@edx/frontend-platform/i18n';

import {
BOOLEAN_FILTERS,
SEARCH_FACET_FILTERS,
ADDITIONAL_FACET_FILTERS,
URL_FILTERS,
} from './data/constants';
import { refinementsReducer } from './data/reducer';
import { setMultipleRefinementsAction } from './data/actions';
import { searchParamsToObject, stringifyRefinements } from './data/utils';
import { getSearchFacetFilters } from './utils';

export const SearchContext = createContext();

Expand Down Expand Up @@ -42,6 +43,10 @@ const SearchData = ({ children, searchFacetFilters, trackingName }) => {
refinementsReducer,
{},
);
const intl = useIntl();
const searchFacetFiltersWithI18n = (
searchFacetFilters === null ? getSearchFacetFilters(intl) : searchFacetFilters
);

const { pathname, search } = useLocation();
const navigate = useNavigate();
Expand All @@ -57,7 +62,7 @@ const SearchData = ({ children, searchFacetFilters, trackingName }) => {
*/
useEffect(() => {
const initialQueryParams = searchParamsToObject(new URLSearchParams(search));
const activeFacetAttributes = searchFacetFilters.map(filter => filter.attribute);
const activeFacetAttributes = searchFacetFiltersWithI18n.map(filter => filter.attribute);
const refinementsToSet = getRefinementsToSet(initialQueryParams, activeFacetAttributes);
dispatch(setMultipleRefinementsAction(refinementsToSet));
}, []);
Expand All @@ -78,10 +83,10 @@ const SearchData = ({ children, searchFacetFilters, trackingName }) => {
() => ({
refinements,
dispatch,
searchFacetFilters,
searchFacetFilters: searchFacetFiltersWithI18n,
trackingName,
}),
[JSON.stringify(refinements), dispatch, searchFacetFilters, trackingName],
[JSON.stringify(refinements), dispatch, searchFacetFiltersWithI18n, trackingName],
);

return (
Expand All @@ -90,7 +95,7 @@ const SearchData = ({ children, searchFacetFilters, trackingName }) => {
};

SearchData.defaultProps = {
searchFacetFilters: SEARCH_FACET_FILTERS,
searchFacetFilters: null,
trackingName: null,
};

Expand Down
20 changes: 17 additions & 3 deletions packages/catalog-search/src/SearchPagination.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connectPagination } from 'react-instantsearch-dom';
import { Pagination, Icon } from '@openedx/paragon';
import { ArrowBackIos, ArrowForwardIos } from '@openedx/paragon/icons';

import { useIntl } from '@edx/frontend-platform/i18n';
import { SearchContext } from './SearchContext';
import { setRefinementAction, deleteRefinementAction } from './data/actions';

Expand All @@ -13,6 +14,7 @@ export const SearchPaginationBase = ({
maxPagesDisplayed,
}) => {
const { dispatch } = useContext(SearchContext);
const intl = useIntl();

const icons = useMemo(
() => ({
Expand All @@ -35,9 +37,21 @@ export const SearchPaginationBase = ({
const buttonLabels = {
previous: '',
next: '',
page: 'Page',
currentPage: 'Current Page',
pageOfCount: 'of',
page: intl.formatMessage({
id: 'catalog.search.pagination.page',
defaultMessage: 'Page',
description: 'Label for the page number in the pagination component',
}),
currentPage: intl.formatMessage({
id: 'catalog.search.pagination.current.page',
defaultMessage: 'Current Page',
description: 'Label for the current page number in the pagination component',
}),
pageOfCount: intl.formatMessage({
id: 'catalog.search.pagination.page.of.count',
defaultMessage: 'of',
description: 'Label for the page of count in the pagination component',
}),
};

const handlePageSelect = (page) => {
Expand Down
Loading

0 comments on commit cb2edf1

Please sign in to comment.