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 {DataSourceEnum, SourceSelection} from './datasource/data_source';
import {Details} from './details/details'; import {Details} from './details/details';
import {EmbeddedDataSource, EmbeddedSourceSpec} from './datasource/embedded'; 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 {getI18nMessage} from './util/error_i18n';
import {IndiInfo} from 'topola'; import {IndiInfo} from 'topola';
import {Intro} from './intro'; 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. */ /** State of the application. */
const [state, setState] = useState<AppState>(AppState.INITIAL); const [state, setState] = useState<AppState>(AppState.INITIAL);
/** Loaded data. */ /** Loaded data. */
@@ -207,6 +207,8 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
const [freezeAnimation, setFreezeAnimation] = useState(false); const [freezeAnimation, setFreezeAnimation] = useState(false);
const [config, setConfig] = useState(DEFALUT_CONFIG); const [config, setConfig] = useState(DEFALUT_CONFIG);
const intl = useIntl();
/** Sets the state with a new individual selection and chart type. */ /** Sets the state with a new individual selection and chart type. */
function updateDisplay(newSelection: IndiInfo) { function updateDisplay(newSelection: IndiInfo) {
if ( if (
@@ -226,7 +228,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
const uploadedDataSource = new UploadedDataSource(); const uploadedDataSource = new UploadedDataSource();
const gedcomUrlDataSource = new GedcomUrlDataSource(); const gedcomUrlDataSource = new GedcomUrlDataSource();
const wikiTreeDataSource = new WikiTreeDataSource(props.intl); const wikiTreeDataSource = new WikiTreeDataSource(intl);
const embeddedDataSource = new EmbeddedDataSource(); const embeddedDataSource = new EmbeddedDataSource();
function isNewData(newSourceSpec: DataSourceSpec, newSelection?: IndiInfo) { function isNewData(newSourceSpec: DataSourceSpec, newSelection?: IndiInfo) {
@@ -328,7 +330,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
setShowSidePanel(args.showSidePanel); setShowSidePanel(args.showSidePanel);
setState(AppState.SHOWING_CHART); setState(AppState.SHOWING_CHART);
} catch (error: any) { } catch (error: any) {
setErrorMessage(getI18nMessage(error, props.intl)); setErrorMessage(getI18nMessage(error, intl));
} }
} else if ( } else if (
state === AppState.SHOWING_CHART || state === AppState.SHOWING_CHART ||
@@ -346,7 +348,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
updateDisplay(newSelection); updateDisplay(newSelection);
if (loadMoreFromWikitree) { if (loadMoreFromWikitree) {
try { 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); const newSelection = getSelection(data.chartData, args.selection);
setData(data); setData(data);
setSelection(newSelection); setSelection(newSelection);
@@ -354,7 +356,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
} catch (error: any) { } catch (error: any) {
setState(AppState.SHOWING_CHART); setState(AppState.SHOWING_CHART);
displayErrorPopup( displayErrorPopup(
props.intl.formatMessage( intl.formatMessage(
{ {
id: 'error.failed_wikitree_load_more', id: 'error.failed_wikitree_load_more',
defaultMessage: 'Failed to load data from WikiTree. {error}', defaultMessage: 'Failed to load data from WikiTree. {error}',
@@ -410,7 +412,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
await downloadPdf(); await downloadPdf();
} catch (e) { } catch (e) {
displayErrorPopup( displayErrorPopup(
props.intl.formatMessage({ intl.formatMessage({
id: 'error.failed_pdf', id: 'error.failed_pdf',
defaultMessage: defaultMessage:
'Failed to generate PDF file.' + 'Failed to generate PDF file.' +
@@ -426,7 +428,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
await downloadPng(); await downloadPng();
} catch (e) { } catch (e) {
displayErrorPopup( displayErrorPopup(
props.intl.formatMessage({ intl.formatMessage({
id: 'error.failed_png', id: 'error.failed_png',
defaultMessage: defaultMessage:
'Failed to generate PNG file.' + 'Failed to generate PNG file.' +
@@ -451,7 +453,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
case AppState.LOADING_MORE: case AppState.LOADING_MORE:
const sidePanelTabs = [ const sidePanelTabs = [
{ {
menuItem: props.intl.formatMessage({ menuItem: intl.formatMessage({
id: 'tab.info', id: 'tab.info',
defaultMessage: 'Info', defaultMessage: 'Info',
}), }),
@@ -460,7 +462,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) {
), ),
}, },
{ {
menuItem: props.intl.formatMessage({ menuItem: intl.formatMessage({
id: 'tab.settings', id: 'tab.settings',
defaultMessage: '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 {ChartColors} from './config';
import {injectIntl, WrappedComponentProps} from 'react-intl';
import {interpolateNumber} from 'd3-interpolate'; import {interpolateNumber} from 'd3-interpolate';
import {IntlShape, useIntl} from 'react-intl';
import {max, min} from 'd3-array'; import {max, min} from 'd3-array';
import {Media} from './util/media'; import {Media} from './util/media';
import {saveAs} from 'file-saver'; import {saveAs} from 'file-saver';
@@ -259,8 +259,6 @@ export interface ChartProps {
colors?: ChartColors; colors?: ChartColors;
} }
type ChartComponentProps = ChartProps & WrappedComponentProps;
class ChartWrapper { class ChartWrapper {
private chart?: ChartHandle; private chart?: ChartHandle;
/** Animation is in progress. */ /** Animation is in progress. */
@@ -270,7 +268,7 @@ class ChartWrapper {
/** The d3 zoom behavior object. */ /** The d3 zoom behavior object. */
private zoomBehavior?: ZoomBehavior<Element, any>; private zoomBehavior?: ZoomBehavior<Element, any>;
/** Props that will be used for rerendering. */ /** Props that will be used for rerendering. */
private rerenderProps?: ChartComponentProps; private rerenderProps?: ChartProps;
zoom(factor: number) { zoom(factor: number) {
const parent = select('#svgContainer') as Selection<Element, any, any, any>; const parent = select('#svgContainer') as Selection<Element, any, any, any>;
@@ -283,7 +281,8 @@ class ChartWrapper {
* animation is performed. * animation is performed.
*/ */
renderChart( renderChart(
props: ChartComponentProps, props: ChartProps,
intl: IntlShape,
args: {initialRender: boolean; resetPosition: boolean} = { args: {initialRender: boolean; resetPosition: boolean} = {
initialRender: false, initialRender: false,
resetPosition: false, resetPosition: false,
@@ -312,7 +311,7 @@ class ChartWrapper {
colors: chartColors.get(props.colors!), colors: chartColors.get(props.colors!),
animate: true, animate: true,
updateSvgSize: false, updateSvgSize: false,
locale: props.intl.locale, locale: intl.locale,
}); });
} else { } else {
this.chart!.setData(props.data); this.chart!.setData(props.data);
@@ -391,7 +390,7 @@ class ChartWrapper {
this.rerenderRequired = false; this.rerenderRequired = false;
// Use `this.rerenderProps` instead of the props in scope because // Use `this.rerenderProps` instead of the props in scope because
// the props may have been updated in the meantime. // the props may have been updated in the meantime.
this.renderChart(this.rerenderProps!, { this.renderChart(this.rerenderProps!, intl, {
initialRender: false, initialRender: false,
resetPosition: false, resetPosition: false,
}); });
@@ -408,9 +407,10 @@ function usePrevious<T>(value: T): T | undefined {
return ref.current; return ref.current;
} }
function ChartComponent(props: ChartComponentProps) { export function Chart(props: ChartProps) {
const chartWrapper = useRef(new ChartWrapper()); const chartWrapper = useRef(new ChartWrapper());
const prevProps = usePrevious(props); const prevProps = usePrevious(props);
const intl = useIntl();
useEffect(() => { useEffect(() => {
if (prevProps) { if (prevProps) {
@@ -418,9 +418,12 @@ function ChartComponent(props: ChartComponentProps) {
props.chartType !== prevProps?.chartType || props.chartType !== prevProps?.chartType ||
props.colors !== prevProps?.colors; props.colors !== prevProps?.colors;
const resetPosition = props.chartType !== prevProps?.chartType; const resetPosition = props.chartType !== prevProps?.chartType;
chartWrapper.current.renderChart(props, {initialRender, resetPosition}); chartWrapper.current.renderChart(props, intl, {
initialRender,
resetPosition,
});
} else { } else {
chartWrapper.current.renderChart(props, { chartWrapper.current.renderChart(props, intl, {
initialRender: true, initialRender: true,
resetPosition: true, resetPosition: true,
}); });
@@ -449,5 +452,3 @@ function ChartComponent(props: ChartComponentProps) {
</div> </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 {DateOrRange, getDate} from 'topola';
import {dereference, GedcomData, getData} from '../util/gedcom_util'; import {dereference, GedcomData, getData} from '../util/gedcom_util';
import {GedcomEntry} from 'parse-gedcom'; import {GedcomEntry} from 'parse-gedcom';
import {injectIntl, IntlShape, WrappedComponentProps} from 'react-intl'; import {IntlShape, useIntl} from 'react-intl';
import {MultilineText} from './multiline-text'; import {MultilineText} from './multiline-text';
import {TranslatedTag} from './translated-tag'; 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) => const events = flatMap(EVENT_TAGS, (tag) =>
props.entries props.entries
.filter((entry) => entry.tag === tag) .filter((entry) => entry.tag === tag)
.map((eventEntry) => .map((eventEntry) => toEvent(eventEntry, props.gedcom, props.indi, intl))
toEvent(eventEntry, props.gedcom, props.indi, props.intl),
)
.flatMap((events) => events) .flatMap((events) => events)
.sort((event1, event2) => compareDates(event1.date, event2.date)) .sort((event1, event2) => compareDates(event1.date, event2.date))
.map((event) => eventDetails(event)), .map((event) => eventDetails(event)),
@@ -208,5 +208,3 @@ function EventsComponent(props: Props & WrappedComponentProps) {
} }
return null; 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 {buildSearchIndex, SearchIndex, SearchResult} from './search_index';
import {formatDateOrRange} from '../util/date_util'; import {formatDateOrRange} from '../util/date_util';
import {IndiInfo, JsonGedcomData} from 'topola'; import {IndiInfo, JsonGedcomData} from 'topola';
import {injectIntl, WrappedComponentProps} from 'react-intl';
import {JsonIndi} from 'topola'; import {JsonIndi} from 'topola';
import {Search, SearchResultProps} from 'semantic-ui-react'; import {Search, SearchResultProps} from 'semantic-ui-react';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
function getNameLine(result: SearchResult) { function getNameLine(result: SearchResult) {
const name = [result.indi.firstName, result.indi.lastName].join(' ').trim(); 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. */ /** 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 [searchResults, setSearchResults] = useState<SearchResultProps[]>([]);
const [searchString, setSearchString] = useState(''); const [searchString, setSearchString] = useState('');
const searchIndex = useRef<SearchIndex>(); const searchIndex = useRef<SearchIndex>();
const intl = useIntl();
function getDescriptionLine(indi: JsonIndi) { function getDescriptionLine(indi: JsonIndi) {
const birthDate = formatDateOrRange(indi.birth, props.intl); const birthDate = formatDateOrRange(indi.birth, intl);
const deathDate = formatDateOrRange(indi.death, props.intl); const deathDate = formatDateOrRange(indi.death, intl);
if (!deathDate) { if (!deathDate) {
return birthDate; return birthDate;
} }
@@ -86,11 +87,11 @@ function SearchBarComponent(props: WrappedComponentProps & Props) {
onSearchChange={(_, data) => onChange(data.value!)} onSearchChange={(_, data) => onChange(data.value!)}
onResultSelect={(_, data) => handleResultSelect(data.result.id)} onResultSelect={(_, data) => handleResultSelect(data.result.id)}
results={searchResults} results={searchResults}
noResultsMessage={props.intl.formatMessage({ noResultsMessage={intl.formatMessage({
id: 'menu.search.no_results', id: 'menu.search.no_results',
defaultMessage: 'No results found', defaultMessage: 'No results found',
})} })}
placeholder={props.intl.formatMessage({ placeholder={intl.formatMessage({
id: 'menu.search.placeholder', id: 'menu.search.placeholder',
defaultMessage: 'Search for people', 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 wikitreeLogo from './wikitree.png';
import {analyticsEvent} from '../util/analytics'; import {analyticsEvent} from '../util/analytics';
import {Button, Form, Header, Input, Modal} from 'semantic-ui-react'; 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 {getLoggedInUserName} from '../datasource/wikitree';
import {MenuItem, MenuType} from './menu_item'; import {MenuItem, MenuType} from './menu_item';
import {RouteComponentProps} from 'react-router-dom'; 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. */ /** 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 formRef = useRef<HTMLFormElement>(null);
const returnUrlRef = useRef<HTMLInputElement>(null); const returnUrlRef = useRef<HTMLInputElement>(null);
const intl = useIntl();
/** /**
* Redirect to the WikiTree Apps login page with a return URL pointing to * 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', id: 'menu.wikitree_popup_username',
defaultMessage: 'Logged in to WikiTree as {username}', defaultMessage: 'Logged in to WikiTree as {username}',
@@ -211,4 +212,3 @@ function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) {
</MenuItem> </MenuItem>
); );
} }
export const WikiTreeLoginMenu = injectIntl(WikiTreeLoginMenuComponent);