Skip to content

Commit

Permalink
Implement i18n for a few components
Browse files Browse the repository at this point in the history
  • Loading branch information
attemoi committed Apr 29, 2024
1 parent d332a77 commit ed45827
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 90 deletions.
10 changes: 8 additions & 2 deletions src/components/AccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import { RootState } from "../store";
import Header from "./Header";
import Footer from "./Footer";
import title from "../utils/title";
import { WithTranslation, withTranslation } from "react-i18next";

export interface AccountPageContext {
editorStore: Store<RootState>;
csrfToken: string;
}

/** Page for configuring account settings. */
export default class AccountPage extends React.Component<object, object> {
export class AccountPage extends React.Component<
object & Partial<WithTranslation>,
object
> {
context: AccountPageContext;

static contextTypes: React.ValidationMap<AccountPageContext> = {
Expand All @@ -35,6 +39,8 @@ export default class AccountPage extends React.Component<object, object> {
}

UNSAFE_componentWillMount() {
document.title = title("Account");
document.title = title(this.props.t("accountPage.documentTitle"));
}
}

export default withTranslation()(AccountPage);
7 changes: 6 additions & 1 deletion src/components/AdvancedSearchFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from "react";
import { AdvancedSearchQuery } from "../interfaces";
import AdvancedSearchBooleanFilter from "./AdvancedSearchBooleanFilter";
import AdvancedSearchValueFilter from "./AdvancedSearchValueFilter";
import { Translation } from "react-i18next";

export interface AdvancedSearchFilterProps {
onBooleanChange: (id: string, bool: string) => void;
Expand Down Expand Up @@ -49,5 +50,9 @@ export default function AdvancedSearchFilter({
);
}

return <span>No filters configured.</span>;
return (
<Translation>
{(t) => <span>{t("advancedSearchFilter.noFiltersConfigured")}</span>}
</Translation>
);
}
48 changes: 22 additions & 26 deletions src/components/CustomListSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CustomListEditorSearchParams } from "../reducers/customListEditor";
import AdvancedSearchBuilder from "./AdvancedSearchBuilder";
import CustomListSearchQueryViewer from "./CustomListSearchQueryViewer";
import EditableInput from "./EditableInput";
import { useTranslation } from "react-i18next";

export interface CustomListSearchProps {
autoUpdate?: boolean;
Expand Down Expand Up @@ -35,12 +36,6 @@ export interface CustomListSearchProps {
selectAdvSearchQuery?: (builderName: string, id: string) => void;
}

const sorts = [
{ value: null, label: "Relevance" },
{ value: "title", label: "Title" },
{ value: "author", label: "Author" },
];

const CustomListSearch = ({
autoUpdate,
entryPoints,
Expand Down Expand Up @@ -72,6 +67,14 @@ const CustomListSearch = ({
}
}, []);

const { t } = useTranslation();

const sorts = [
{ value: null, label: t("common.labelRelevance") },
{ value: "title", label: t("common.labelTitle") },
{ value: "author", label: t("common.labelAuthor") },
];

const readOnly = !isOwner;

const entryPointsWithAll = entryPoints.includes("All")
Expand Down Expand Up @@ -102,7 +105,7 @@ const CustomListSearch = ({
const searchForm = (
<div className="search-titles">
<div className="entry-points">
<span>Search for:</span>
<span>{t("customListSearch.searchFor")}</span>

<div className="entry-points-selection">
{entryPointsWithAll.map((entryPoint) => (
Expand All @@ -121,7 +124,7 @@ const CustomListSearch = ({
</div>

<div className="search-options">
<span>Sort by:</span>
<span>{t("customListSearch.sortByLabel")}</span>

<div className="search-options-selection">
{sorts.map(({ value, label }) => (
Expand All @@ -138,23 +141,19 @@ const CustomListSearch = ({
))}
</div>

<aside>
Results can be sorted by attributes that are enabled in this library's
Lanes &amp; Filters configuration. Selecting "Title" or "Author" will
automatically filter out less relevant results.
</aside>
<aside>{t("customListSearch.sortByDescription")}</aside>
</div>

<div className="search-builders">
<Panel
headerText="Works to include"
headerText={t("customListSearch.worksToInclude")}
id="search-filters-include"
openByDefault={true}
content={renderAdvancedSearchBuilder("include")}
/>

<Panel
headerText="Works to exclude"
headerText={t("customListSearch.worksToExclude")}
id="search-filters-exclude"
openByDefault={false}
content={renderAdvancedSearchBuilder("exclude")}
Expand All @@ -170,7 +169,7 @@ const CustomListSearch = ({

{showAutoUpdate && (
<div className="auto-update">
<span>Use search to:</span>
<span>{t("customListSearch.useSearchTo")}</span>

<div className="auto-update-selection">
<div>
Expand All @@ -179,15 +178,14 @@ const CustomListSearch = ({
type="radio"
name="auto-update"
checked={autoUpdate}
label={"Automatically update this list"}
label={t("customListSearch.automaticUpdateLabel")}
onChange={() => updateAutoUpdate?.(true)}
/>

<aside>
The system will periodically execute this search and
automatically update the list with the results.
{t("customListSearch.automaticUpdateDescription")}{" "}
{!readOnly &&
"The search results below represent the titles that would be in the list if it were updated now, but the actual contents of the list will change over time."}
t("customListSearch.automaticUpdateDescriptionExtra")}
</aside>
</div>

Expand All @@ -197,16 +195,14 @@ const CustomListSearch = ({
type="radio"
name="auto-update"
checked={!autoUpdate}
label={"Manually select titles"}
label={t("customListSearch.manualSelectLabel")}
onChange={() => updateAutoUpdate?.(false)}
/>

<aside>
The list entries are manually selected.{" "}
{t("customListSearch.manualSelectDescription")}{" "}
{!readOnly &&
"Move the desired titles from the search results column on the left to the column on the right to add them to the list. "}
Titles may be removed from the list automatically if they become
unavailable, but the list is otherwise fixed.
t("customListSearch.manualSelectDescriptionExtra")}
</aside>
</div>
</div>
Expand All @@ -217,7 +213,7 @@ const CustomListSearch = ({

return (
<Panel
headerText="Search for titles"
headerText={t("customListSearch.searchForTitles")}
id="search-titles"
openByDefault={true}
onEnter={search}
Expand Down
61 changes: 42 additions & 19 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Router } from "@thepalaceproject/web-opds-client/lib/interfaces";
import { Button } from "library-simplified-reusable-components";
import { GenericWedgeIcon } from "@nypl/dgx-svg-icons";
import title from "../utils/title";
import { withTranslation, WithTranslation } from "react-i18next";

const palaceLogoUrl = require("../images/PalaceCollectionManagerLogo.svg")
.default;
Expand Down Expand Up @@ -41,7 +42,8 @@ export interface HeaderProps
extends React.Props<Header>,
HeaderStateProps,
HeaderDispatchProps,
HeaderOwnProps {}
HeaderOwnProps,
Partial<WithTranslation> {}

export interface HeaderState {
showAccountDropdown: boolean;
Expand Down Expand Up @@ -112,30 +114,42 @@ export class Header extends React.Component<HeaderProps, HeaderState> {
let isSiteWide = !this.context.library || !currentLibrary;
let isSomeLibraryManager = this.context.admin.isLibraryManagerOfSomeLibrary();

const { t } = this.props;

// Dashboard link that will be rendered in a Link router component.
const dashboardLinkItem = {
label: "Dashboard",
label: t("header.menuLabelDashboard"),
href: "dashboard/",
hidden: this.isEkirjasto,
};

const finlandStatisticsLinkItem = {
label: "Tilastot",
label: t("header.menuLabelStatistics"),
href: "statistics/",
hidden: !this.isEkirjasto || !isSystemAdmin,
};

// Links that will be rendered in a NavItem Bootstrap component.
const libraryNavItems = [
{ label: "Catalog", href: "%2Fgroups?max_cache_age=0" },
{ label: "Hidden Books", href: "%2Fadmin%2Fsuppressed" },
{
label: t("header.menuLabelCatalog"),
href: "%2Fgroups?max_cache_age=0",
},
{
label: t("header.menuLabelHiddenBooks"),
href: "%2Fadmin%2Fsuppressed",
},
];
// Other links that will be rendered in a Link router component and are library specific.
const libraryLinkItems = [
{ label: "Lists", href: "lists/" },
{ label: "Lanes", href: "lanes/", auth: isLibraryManager },
{ label: t("header.menuLabelLists"), href: "lists/" },
{
label: "Patrons",
label: t("header.menuLabelLanes"),
href: "lanes/",
auth: isLibraryManager,
},
{
label: t("header.menuLabelPatrons"),
href: "patrons/",
auth: isSystemAdmin,
hidden: this.isEkirjasto,
Expand All @@ -144,7 +158,7 @@ export class Header extends React.Component<HeaderProps, HeaderState> {
// Links that will be rendered in a Link router component and are sitewide.
const sitewideLinkItems = [
{
label: "Dashboard",
label: t("header.menuLabelDashboard"),
href: "dashboard/",
auth:
// Finland: Palace shows dashboard for all roles
Expand All @@ -153,20 +167,20 @@ export class Header extends React.Component<HeaderProps, HeaderState> {
(this.isEkirjasto && isSiteWide && isSystemAdmin),
},
{
label: "System Configuration",
label: t("header.menuLabelSystemConfiguration"),
href: "config/",
// Finland: Palace shows system config for all roles.
// For E-kirjasto limit to system admins only.
auth: !this.isEkirjasto || isSystemAdmin,
},
{
label: "Troubleshooting",
label: t("header.menuLabelTroubleshooting"),
href: "troubleshooting/",
auth: isSystemAdmin,
},
];
const accountLink = {
label: "Change password",
label: t("header.menuLabelChangePassword"),
href: "account/",
hidden: this.context.admin.isEkirjastoUser(),
};
Expand All @@ -181,19 +195,24 @@ export class Header extends React.Component<HeaderProps, HeaderState> {
ref={this.libraryRef}
value={currentLibrary}
onChange={this.changeLibrary}
aria-label="Select a library"
aria-label={t("header.aria.libraryDropdownLabel")}
>
{(!this.context.library || !currentLibrary) && (
<option aria-selected={false}>Select a library</option>
<option aria-selected={false}>
{t("header.libraryDropdownFirstOption")}
</option>
)}
{this.props.libraries.map((library) => (
<option
key={library.short_name}
value={library.short_name}
aria-selected={currentLibrary === library.short_name}
>
{library.name || library.short_name}
{library.is_default && " (default)"}
{library.is_default
? t("header.libraryDropdownDefaultLibrary", {
libraryName: library.name || library.short_name,
})
: library.name || library.short_name}
</option>
))}
</EditableInput>
Expand Down Expand Up @@ -246,7 +265,9 @@ export class Header extends React.Component<HeaderProps, HeaderState> {
{this.displayPermissions(isSystemAdmin, isLibraryManager)}
{this.renderLinkItem(accountLink, currentPathname)}
<li>
<a href="/admin/sign_out">Sign out</a>
<a href="/admin/sign_out">
{t("header.accountDropdownSignout")}
</a>
</li>
</ul>
)}
Expand Down Expand Up @@ -373,14 +394,16 @@ function mapDispatchToProps(dispatch) {
};
}

export const HeaderWithTranslation = withTranslation()(Header);

const ConnectedHeader = connect<
HeaderStateProps,
HeaderDispatchProps,
HeaderOwnProps
>(
mapStateToProps,
mapDispatchToProps
)(Header);
)(HeaderWithTranslation);

/** HeaderWithStore is a wrapper component to pass the store as a prop to the
ConnectedHeader, since it's not in the regular place in the context. */
Expand All @@ -392,6 +415,6 @@ export default class HeaderWithStore extends React.Component<{}, {}> {
};

render(): JSX.Element {
return <ConnectedHeader store={this.context.editorStore} />;
return <ConnectedHeader store={this.context.editorStore} {...this.props} />;
}
}
Loading

0 comments on commit ed45827

Please sign in to comment.