Use useIntl hook for i18n

This commit is contained in:
Przemek Wiech
2021-11-04 16:58:11 +01:00
parent 3059853807
commit 6e8b6c7b9e
5 changed files with 41 additions and 42 deletions

View File

@@ -5,7 +5,7 @@ import {Changelog} from './changelog';
import {DataSourceEnum, SourceSelection} from './datasource/data_source';
import {Details} from './details/details';
import {EmbeddedDataSource, EmbeddedSourceSpec} from './datasource/embedded';
import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';
import {FormattedMessage, useIntl} from 'react-intl';
import {getI18nMessage} from './util/error_i18n';
import {IndiInfo} from 'topola';
import {Intro} from './intro';
@@ -184,7 +184,7 @@ function hasUpdatedValues<T>(state: T, changes: Partial<T> | undefined) {
);
}
function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
export function App(props: RouteComponentProps) {
/** State of the application. */
const [state, setState] = useState<AppState>(AppState.INITIAL);
/** Loaded data. */
@@ -207,6 +207,8 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
const [freezeAnimation, setFreezeAnimation] = useState(false);
const [config, setConfig] = useState(DEFALUT_CONFIG);
const intl = useIntl();
/** Sets the state with a new individual selection and chart type. */
function updateDisplay(newSelection: IndiInfo) {
if (
@@ -226,7 +228,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
const uploadedDataSource = new UploadedDataSource();
const gedcomUrlDataSource = new GedcomUrlDataSource();
const wikiTreeDataSource = new WikiTreeDataSource(props.intl);
const wikiTreeDataSource = new WikiTreeDataSource(intl);
const embeddedDataSource = new EmbeddedDataSource();
function isNewData(newSourceSpec: DataSourceSpec, newSelection?: IndiInfo) {
@@ -328,7 +330,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
setShowSidePanel(args.showSidePanel);
setState(AppState.SHOWING_CHART);
} catch (error: any) {
setErrorMessage(getI18nMessage(error, props.intl));
setErrorMessage(getI18nMessage(error, intl));
}
} else if (
state === AppState.SHOWING_CHART ||
@@ -346,7 +348,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
updateDisplay(newSelection);
if (loadMoreFromWikitree) {
try {
const data = await loadWikiTree(args.selection!.id, props.intl);
const data = await loadWikiTree(args.selection!.id, intl);
const newSelection = getSelection(data.chartData, args.selection);
setData(data);
setSelection(newSelection);
@@ -354,7 +356,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
} catch (error: any) {
setState(AppState.SHOWING_CHART);
displayErrorPopup(
props.intl.formatMessage(
intl.formatMessage(
{
id: 'error.failed_wikitree_load_more',
defaultMessage: 'Failed to load data from WikiTree. {error}',
@@ -410,7 +412,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
await downloadPdf();
} catch (e) {
displayErrorPopup(
props.intl.formatMessage({
intl.formatMessage({
id: 'error.failed_pdf',
defaultMessage:
'Failed to generate PDF file.' +
@@ -426,7 +428,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
await downloadPng();
} catch (e) {
displayErrorPopup(
props.intl.formatMessage({
intl.formatMessage({
id: 'error.failed_png',
defaultMessage:
'Failed to generate PNG file.' +
@@ -451,7 +453,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
case AppState.LOADING_MORE:
const sidePanelTabs = [
{
menuItem: props.intl.formatMessage({
menuItem: intl.formatMessage({
id: 'tab.info',
defaultMessage: 'Info',
}),
@@ -460,7 +462,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
),
},
{
menuItem: props.intl.formatMessage({
menuItem: intl.formatMessage({
id: 'tab.settings',
defaultMessage: 'Settings',
}),
@@ -546,5 +548,3 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
</>
);
}
export const App = injectIntl(AppComponent);

View File

@@ -1,6 +1,6 @@
import {ChartColors} from './config';
import {injectIntl, WrappedComponentProps} from 'react-intl';
import {interpolateNumber} from 'd3-interpolate';
import {IntlShape, useIntl} from 'react-intl';
import {max, min} from 'd3-array';
import {Media} from './util/media';
import {saveAs} from 'file-saver';
@@ -259,8 +259,6 @@ export interface ChartProps {
colors?: ChartColors;
}
type ChartComponentProps = ChartProps & WrappedComponentProps;
class ChartWrapper {
private chart?: ChartHandle;
/** Animation is in progress. */
@@ -270,7 +268,7 @@ class ChartWrapper {
/** The d3 zoom behavior object. */
private zoomBehavior?: ZoomBehavior<Element, any>;
/** Props that will be used for rerendering. */
private rerenderProps?: ChartComponentProps;
private rerenderProps?: ChartProps;
zoom(factor: number) {
const parent = select('#svgContainer') as Selection<Element, any, any, any>;
@@ -283,7 +281,8 @@ class ChartWrapper {
* animation is performed.
*/
renderChart(
props: ChartComponentProps,
props: ChartProps,
intl: IntlShape,
args: {initialRender: boolean; resetPosition: boolean} = {
initialRender: false,
resetPosition: false,
@@ -312,7 +311,7 @@ class ChartWrapper {
colors: chartColors.get(props.colors!),
animate: true,
updateSvgSize: false,
locale: props.intl.locale,
locale: intl.locale,
});
} else {
this.chart!.setData(props.data);
@@ -391,7 +390,7 @@ class ChartWrapper {
this.rerenderRequired = false;
// Use `this.rerenderProps` instead of the props in scope because
// the props may have been updated in the meantime.
this.renderChart(this.rerenderProps!, {
this.renderChart(this.rerenderProps!, intl, {
initialRender: false,
resetPosition: false,
});
@@ -408,9 +407,10 @@ function usePrevious<T>(value: T): T | undefined {
return ref.current;
}
function ChartComponent(props: ChartComponentProps) {
export function Chart(props: ChartProps) {
const chartWrapper = useRef(new ChartWrapper());
const prevProps = usePrevious(props);
const intl = useIntl();
useEffect(() => {
if (prevProps) {
@@ -418,9 +418,12 @@ function ChartComponent(props: ChartComponentProps) {
props.chartType !== prevProps?.chartType ||
props.colors !== prevProps?.colors;
const resetPosition = props.chartType !== prevProps?.chartType;
chartWrapper.current.renderChart(props, {initialRender, resetPosition});
chartWrapper.current.renderChart(props, intl, {
initialRender,
resetPosition,
});
} else {
chartWrapper.current.renderChart(props, {
chartWrapper.current.renderChart(props, intl, {
initialRender: true,
resetPosition: true,
});
@@ -449,5 +452,3 @@ function ChartComponent(props: ChartComponentProps) {
</div>
);
}
export const Chart = injectIntl(ChartComponent);

View File

@@ -3,7 +3,7 @@ import {compareDates, translateDate} from '../util/date_util';
import {DateOrRange, getDate} from 'topola';
import {dereference, GedcomData, getData} from '../util/gedcom_util';
import {GedcomEntry} from 'parse-gedcom';
import {injectIntl, IntlShape, WrappedComponentProps} from 'react-intl';
import {IntlShape, useIntl} from 'react-intl';
import {MultilineText} from './multiline-text';
import {TranslatedTag} from './translated-tag';
@@ -184,13 +184,13 @@ function toFamilyEvents(
});
}
function EventsComponent(props: Props & WrappedComponentProps) {
export function Events(props: Props) {
const intl = useIntl();
const events = flatMap(EVENT_TAGS, (tag) =>
props.entries
.filter((entry) => entry.tag === tag)
.map((eventEntry) =>
toEvent(eventEntry, props.gedcom, props.indi, props.intl),
)
.map((eventEntry) => toEvent(eventEntry, props.gedcom, props.indi, intl))
.flatMap((events) => events)
.sort((event1, event2) => compareDates(event1.date, event2.date))
.map((event) => eventDetails(event)),
@@ -208,5 +208,3 @@ function EventsComponent(props: Props & WrappedComponentProps) {
}
return null;
}
export const Events = injectIntl(EventsComponent);

View File

@@ -3,10 +3,10 @@ import {analyticsEvent} from '../util/analytics';
import {buildSearchIndex, SearchIndex, SearchResult} from './search_index';
import {formatDateOrRange} from '../util/date_util';
import {IndiInfo, JsonGedcomData} from 'topola';
import {injectIntl, WrappedComponentProps} from 'react-intl';
import {JsonIndi} from 'topola';
import {Search, SearchResultProps} from 'semantic-ui-react';
import {useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
function getNameLine(result: SearchResult) {
const name = [result.indi.firstName, result.indi.lastName].join(' ').trim();
@@ -27,14 +27,15 @@ interface Props {
}
/** Displays and handles the search box in the top bar. */
function SearchBarComponent(props: WrappedComponentProps & Props) {
export function SearchBar(props: Props) {
const [searchResults, setSearchResults] = useState<SearchResultProps[]>([]);
const [searchString, setSearchString] = useState('');
const searchIndex = useRef<SearchIndex>();
const intl = useIntl();
function getDescriptionLine(indi: JsonIndi) {
const birthDate = formatDateOrRange(indi.birth, props.intl);
const deathDate = formatDateOrRange(indi.death, props.intl);
const birthDate = formatDateOrRange(indi.birth, intl);
const deathDate = formatDateOrRange(indi.death, intl);
if (!deathDate) {
return birthDate;
}
@@ -86,11 +87,11 @@ function SearchBarComponent(props: WrappedComponentProps & Props) {
onSearchChange={(_, data) => onChange(data.value!)}
onResultSelect={(_, data) => handleResultSelect(data.result.id)}
results={searchResults}
noResultsMessage={props.intl.formatMessage({
noResultsMessage={intl.formatMessage({
id: 'menu.search.no_results',
defaultMessage: 'No results found',
})}
placeholder={props.intl.formatMessage({
placeholder={intl.formatMessage({
id: 'menu.search.placeholder',
defaultMessage: 'Search for people',
})}
@@ -100,4 +101,3 @@ function SearchBarComponent(props: WrappedComponentProps & Props) {
/>
);
}
export const SearchBar = injectIntl(SearchBarComponent);

View File

@@ -2,7 +2,7 @@ import * as queryString from 'query-string';
import wikitreeLogo from './wikitree.png';
import {analyticsEvent} from '../util/analytics';
import {Button, Form, Header, Input, Modal} from 'semantic-ui-react';
import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';
import {FormattedMessage, useIntl} from 'react-intl';
import {getLoggedInUserName} from '../datasource/wikitree';
import {MenuItem, MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom';
@@ -153,9 +153,10 @@ export function WikiTreeMenu(props: RouteComponentProps & Props) {
}
/** Displays and handles the "Log in to WikiTree" menu. */
function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) {
export function WikiTreeLoginMenu(props: Props) {
const formRef = useRef<HTMLFormElement>(null);
const returnUrlRef = useRef<HTMLInputElement>(null);
const intl = useIntl();
/**
* Redirect to the WikiTree Apps login page with a return URL pointing to
@@ -194,7 +195,7 @@ function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) {
</>
);
}
const tooltip = props.intl.formatMessage(
const tooltip = intl.formatMessage(
{
id: 'menu.wikitree_popup_username',
defaultMessage: 'Logged in to WikiTree as {username}',
@@ -211,4 +212,3 @@ function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) {
</MenuItem>
);
}
export const WikiTreeLoginMenu = injectIntl(WikiTreeLoginMenuComponent);