mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-03-11 18:13:43 +00:00
Use useIntl hook for i18n
This commit is contained in:
24
src/app.tsx
24
src/app.tsx
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user