Skip to content

Commit

Permalink
feat: Added scripts and marked strings for i18n. (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleem-latif authored May 21, 2024
1 parent f17405e commit a0fb639
Show file tree
Hide file tree
Showing 25 changed files with 493 additions and 126 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
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
13 changes: 11 additions & 2 deletions packages/catalog-search/src/FacetListBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import React, { useCallback, useContext } from 'react';
import PropTypes from 'prop-types';

import { NO_OPTIONS_FOUND, STYLE_VARIANTS } from './data/constants';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { STYLE_VARIANTS } from './data/constants';
import FacetDropdown from './FacetDropdown';
import TypeaheadFacetDropdown from './TypeaheadFacetDropdown';
import FacetItem from './FacetItem';
Expand Down Expand Up @@ -62,7 +63,15 @@ const FacetListBase = ({
const renderItems = useCallback(
() => {
if (!items?.length) {
return <span className="p-2 d-block">{NO_OPTIONS_FOUND}</span>;
return (
<span className="p-2 d-block">
<FormattedMessage
id="search.facetFilters.noOptionsFound"
defaultMessage="No options found."
description="Message displayed when no options are found for a facet filter."
/>
</span>
);
}

return items.map((item) => {
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
41 changes: 35 additions & 6 deletions packages/catalog-search/src/MobileFilterMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { connectCurrentRefinements } from 'react-instantsearch-dom';
import { Button } from '@openedx/paragon';
import { ArrowDropDown, Close } from '@openedx/paragon/icons';

import { FormattedMessage } from '@edx/frontend-platform/i18n';
import ClearCurrentRefinements from './ClearCurrentRefinements';

import { useActiveRefinementsAsFlatArray } from './data/hooks';
Expand All @@ -22,10 +23,19 @@ export const MobileFilterMenuBase = ({ children, className, items }) => {
onClick={() => setIsOpen(true)}
>
<div className="mr-2">
Filters
<FormattedMessage
id="catalog.search.filters"
defaultMessage="Filters"
description="Label for the filters button."
/>
{activeRefinementsAsFlatArray && activeRefinementsAsFlatArray.length > 0 && (
<span className="ml-1">
({activeRefinementsAsFlatArray.length} selected)
<FormattedMessage
id="catalog.search.filters.selected"
defaultMessage="({count} selected)"
description="Label for the number of selected filters."
values={{ count: activeRefinementsAsFlatArray.length }}
/>
</span>
)}
</div>
Expand All @@ -45,10 +55,19 @@ export const MobileFilterMenuBase = ({ children, className, items }) => {
<div className="modal-content">
<div className="modal-header d-flex align-items-center">
<h5 className="modal-title text-center w-100">
All Filters
<FormattedMessage
id="catalog.search.filters.all"
defaultMessage="All Filters"
description="Label for the all filters button."
/>
{activeRefinementsAsFlatArray && activeRefinementsAsFlatArray.length > 0 && (
<span className="ml-1">
({activeRefinementsAsFlatArray.length} selected)
<FormattedMessage
id="catalog.search.filters.all.selected"
defaultMessage="({count} selected)"
description="Label for the number of selected filters."
values={{ count: activeRefinementsAsFlatArray.length }}
/>
</span>
)}
</h5>
Expand All @@ -60,7 +79,13 @@ export const MobileFilterMenuBase = ({ children, className, items }) => {
<Close
id="icon-close-mobile-filter-menu"
/>
<span className="sr-only">close filter menu</span>
<span className="sr-only">
<FormattedMessage
id="catalog.search.filters.close"
defaultMessage="close filter menu"
description="Label for the close filter menu button."
/>
</span>
</Button>
</div>
<div className="modal-body p-0">
Expand All @@ -75,7 +100,11 @@ export const MobileFilterMenuBase = ({ children, className, items }) => {
className="btn-brand-primary btn-block py-2 m-0"
onClick={() => setIsOpen(false)}
>
Done
<FormattedMessage
id="catalog.search.filters.done"
defaultMessage="Done"
description="Label for the done button."
/>
</Button>
</div>
</div>
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
Loading

0 comments on commit a0fb639

Please sign in to comment.