From 7f808c1cca1b19fe79306b8129ecee25275af1e3 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 21 Oct 2025 12:30:55 -0400 Subject: [PATCH 1/7] Fix regression in dashboard charts --- .../statistics/jsx/widgets/helpers/queryChartForm.js | 12 ++++++------ modules/statistics/jsx/widgets/recruitment.js | 2 +- .../statistics/locale/ja/LC_MESSAGES/statistics.po | 10 ++++++++++ modules/statistics/locale/statistics.pot | 9 +++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/modules/statistics/jsx/widgets/helpers/queryChartForm.js b/modules/statistics/jsx/widgets/helpers/queryChartForm.js index 0ccc7f17c3f..8b7596eeefc 100644 --- a/modules/statistics/jsx/widgets/helpers/queryChartForm.js +++ b/modules/statistics/jsx/widgets/helpers/queryChartForm.js @@ -90,7 +90,7 @@ const QueryChartForm = (props) => { {t('Project', {ns: 'loris'})} {
+ display: 'block'}}>{t('Cohort', {ns: 'loris', count: 1})} {
+ display: 'block'}}>{t('Visit', {ns: 'loris'})} {
+ display: 'block'}}>{t('Date Registered', {ns: 'statistics'})} { style={{width: '100%', padding: '8px', borderRadius: '5px', border: '1px solid #ccc'}} - label={'Range Start'} + label={t('Range Start', {ns: 'statistics'})} /> { style={{width: '100%', padding: '8px', borderRadius: '5px', border: '1px solid #ccc'}} - label={'Range End'} + label={t('Range End', {ns: 'statistics'})} />
diff --git a/modules/statistics/jsx/widgets/recruitment.js b/modules/statistics/jsx/widgets/recruitment.js index d1f5b41a36e..0b202c0324b 100644 --- a/modules/statistics/jsx/widgets/recruitment.js +++ b/modules/statistics/jsx/widgets/recruitment.js @@ -91,7 +91,7 @@ const Recruitment = (props) => { className="btn btn-default btn-xs" onClick={() => setShowFiltersBreakdown((prev) => !prev)} > - {showFiltersBreakdown ? 'Hide Filters' : 'Show Filters'} + {showFiltersBreakdown ? t('Hide Filters', {ns: 'loris'}) : t('Show Filters', {ns: 'loris'})}
{showFiltersBreakdown && ( diff --git a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po index 006cd523ce2..169d066a9ff 100644 --- a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po @@ -47,3 +47,13 @@ msgstr "PNGとしてダウンロード" msgid "Labels" msgstr "ラベル" + +msgid "Date Registered" +msgstr "登録日" + +msgid "Range Start" +msgstr "範囲開始" + +msgid "Range End" +msgstr "範囲終了" + diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index 61eee2642f3..14ad546ffa2 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -47,3 +47,12 @@ msgstr "" msgid "Labels" msgstr "" + +msgid "Date Registered" +msgstr "" + +msgid "Range Start" +msgstr "" + +msgid "Range End" +msgstr "" From b8cda99bdb6f61c97447230e9d77eadd5b847d43 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 21 Oct 2025 13:34:11 -0400 Subject: [PATCH 2/7] Translate charts --- modules/statistics/jsx/WidgetIndex.js | 6 ++- .../jsx/widgets/helpers/chartBuilder.js | 10 ++--- .../jsx/widgets/helpers/progressbarBuilder.js | 16 ++++---- modules/statistics/jsx/widgets/recruitment.js | 40 ++++++++++++++----- .../jsx/widgets/studyprogression.js | 3 +- .../locale/ja/LC_MESSAGES/statistics.po | 36 +++++++++++++++++ modules/statistics/locale/statistics.pot | 36 +++++++++++++++++ modules/statistics/php/widgets.class.inc | 2 +- 8 files changed, 123 insertions(+), 26 deletions(-) diff --git a/modules/statistics/jsx/WidgetIndex.js b/modules/statistics/jsx/WidgetIndex.js index 1aa73e7847e..fed2202246c 100644 --- a/modules/statistics/jsx/WidgetIndex.js +++ b/modules/statistics/jsx/WidgetIndex.js @@ -61,6 +61,7 @@ const WidgetIndex = (props) => { } ); setupCharts( + t, false, { [section]: { @@ -86,6 +87,7 @@ const WidgetIndex = (props) => { onClick ={() => { setModalChart(chartDetails[section][chartID]); setupCharts( + t, true, { [section]: @@ -215,7 +217,9 @@ const WidgetIndex = (props) => { ...clearedChartDetails[section][chart], filters: queryString, }; - const chartPromise = setupCharts(false, + const chartPromise = setupCharts( + t, + false, {[section]: {[chart]: newChart}}, t('Total', {ns: 'loris'}), ).then( diff --git a/modules/statistics/jsx/widgets/helpers/chartBuilder.js b/modules/statistics/jsx/widgets/helpers/chartBuilder.js index d4c75c686f1..dc0764a495b 100644 --- a/modules/statistics/jsx/widgets/helpers/chartBuilder.js +++ b/modules/statistics/jsx/widgets/helpers/chartBuilder.js @@ -107,7 +107,7 @@ const createPieChart = (columns, id, targetModal, colours) => { return newChart; } -const createBarChart = (labels, columns, id, targetModal, colours, dataType) => { +const createBarChart = (t, labels, columns, id, targetModal, colours, dataType) => { let newChart = c3.generate({ bindto: targetModal ? targetModal : id, data: { @@ -134,7 +134,7 @@ const createBarChart = (labels, columns, id, targetModal, colours, dataType) => }, y: { label: { - text: 'Candidates registered', + text: t('Candidates registered', { ns: 'statistics'}), position: 'inner-top' }, }, @@ -278,7 +278,7 @@ const unloadCharts = (chartDetails, section) => { * This is determined by the original chart type of the data provided from the API * If data was provided as a Pie, and the requested chartType is Bar, then the data will be reformatted */ -const setupCharts = async (targetIsModal, chartDetails, totalLabel) => { +const setupCharts = async (t, targetIsModal, chartDetails, totalLabel) => { const chartPromises = []; let newChartDetails = {...chartDetails} Object.keys(chartDetails).forEach((section) => { @@ -314,7 +314,7 @@ const setupCharts = async (targetIsModal, chartDetails, totalLabel) => { if (chart.chartType === 'pie') { chartObject = createPieChart(columns, `#${chartID}`, targetIsModal && '#dashboardModal', colours); } else if (chart.chartType === 'bar') { - chartObject = createBarChart(labels, columns, `#${chartID}`, targetIsModal && '#dashboardModal', colours, chart.dataType); + chartObject = createBarChart(t, labels, columns, `#${chartID}`, targetIsModal && '#dashboardModal', colours, chart.dataType); } else if (chart.chartType === 'line') { chartObject = createLineChart(chartData, columns, `#${chartID}`, chart.label, targetIsModal && '#dashboardModal', chart.titlePrefix); } @@ -382,4 +382,4 @@ export { // recruitment.js and studyProgression.js setupCharts, unloadCharts, -}; \ No newline at end of file +}; diff --git a/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js b/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js index 8cc94cfdcbb..ce780240327 100644 --- a/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js +++ b/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js @@ -4,7 +4,7 @@ * @param {object} data - data needed to generate the graph content. * @return {JSX.Element} the charts to render to the widget panel. */ -const progressBarBuilder = (data) => { +const progressBarBuilder = (t, data) => { let title; let content; title =
@@ -54,14 +54,14 @@ const progressBarBuilder = (data) => { }

- Target: {data['recruitment_target']} + {t('Target: {{target}}', {target: data['recruitment_target'], ns: 'statistics'})}

{ data['recruitment_target'] && - Recruitment target of {data['recruitment_target']} was reached. - {' '}{data['total_recruitment']} total participants. + {t('Recruitment target of {{target}} was reached.', { 'target': data['recruitment_target'], ns: 'statistics'})} + {' '}{t('{{total}} total participants.', { 'total': data['total_recruitment'], ns: 'statistics'})} } @@ -112,18 +112,18 @@ const progressBarBuilder = (data) => { { data['recruitment_target'] ?

- Target: {data['recruitment_target']} + {t('Target: {{target}}', { 'target': data['recruitment_target'], ns: 'statistics'})}

:

- No target set + {t('No target set', {ns: 'statistics'})}

} { data['recruitment_target'] && - Recruitment target of {data['recruitment_target']} not reached. - {' '}{data['total_recruitment']} total participants. + {t('Recruitment target of {{target}} was not reached.', { 'target': data['recruitment_target'], ns: 'statistics'})} + {' '}{t('{{total}} total participants.', { 'total': data['total_recruitment'], ns: 'statistics'})} } diff --git a/modules/statistics/jsx/widgets/recruitment.js b/modules/statistics/jsx/widgets/recruitment.js index 0b202c0324b..82d11e6910f 100644 --- a/modules/statistics/jsx/widgets/recruitment.js +++ b/modules/statistics/jsx/widgets/recruitment.js @@ -1,11 +1,13 @@ import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; +import i18n from 'I18nSetup'; import Loader from 'Loader'; import Panel from 'Panel'; import {QueryChartForm} from './helpers/queryChartForm'; import {progressBarBuilder} from './helpers/progressbarBuilder'; import {useTranslation} from 'react-i18next'; import {setupCharts} from './helpers/chartBuilder'; +import jaStrings from '../../locale/ja/LC_MESSAGES/statistics.json'; /** * Recruitment - a widget containing statistics for recruitment data. @@ -24,7 +26,7 @@ const Recruitment = (props) => { { 'generalBreakdown': { 'agerecruitment_pie': { - title: 'Total recruitment by Age', + title: t('Total recruitment by Age', { ns: 'statistics' }), filters: '', chartType: 'pie', dataType: 'pie', @@ -34,7 +36,7 @@ const Recruitment = (props) => { chartObject: null, }, 'ethnicity_pie': { - title: 'Ethnicity at Screening', + title: t('Ethnicity at Screening', {ns: 'statistics'}), filters: '', chartType: 'pie', dataType: 'pie', @@ -46,7 +48,7 @@ const Recruitment = (props) => { }, 'siteBreakdown': { 'siterecruitment_pie': { - title: 'Total Recruitment per Site', + title: t('Total Recruitment per Site', {ns: 'statistics'}), filters: '', chartType: 'pie', dataType: 'pie', @@ -56,7 +58,7 @@ const Recruitment = (props) => { chartObject: null, }, 'siterecruitment_bysex': { - title: 'Biological sex breakdown by site', + title: t('Biological sex breakdown by site', {ns: 'statistics'}), filters: '', chartType: 'bar', dataType: 'bar', @@ -67,7 +69,7 @@ const Recruitment = (props) => { }, 'projectBreakdown': { 'agedistribution_line': { - title: 'Candidate Age at Registration', + title: t('Candidate Age at Registration', {ns: 'statistics'}), filters: '', chartType: 'line', dataType: 'line', @@ -79,6 +81,24 @@ const Recruitment = (props) => { } ); + useEffect( () => { + i18n.addResourceBundle('ja', 'statistics', jaStrings); + + // Re-set default state that depended on the translation + let newdetails = {...chartDetails}; + newdetails['generalBreakdown']['agerecruitment_pie']['title'] + = t('Total recruitment by Age', { ns: 'statistics' }); + newdetails['generalBreakdown']['ethnicity_pie']['title'] + = t('Ethnicity at Screening', {ns: 'statistics'}); + newdetails['siteBreakdown']['siterecruitment_pie']['title'] + = t('Total Recruitment per Site', {ns: 'statistics'}); + newdetails['siteBreakdown']['siterecruitment_bysex']['title'] + = t('Biological sex breakdown by site', {ns: 'statistics'}); + newdetails['projectBreakdown']['agedistribution_line']['title'] + = t('Candidate Age at Registration', {ns: 'statistics'}); + setChartDetails(newdetails); + }, []); + const showChart = (section, chartID) => { return props.showChart(section, chartID, chartDetails, setChartDetails); }; @@ -129,7 +149,7 @@ const Recruitment = (props) => { useEffect( () => { if (json && Object.keys(json).length !== 0) { - setupCharts(false, chartDetails, t('Total', {ns: 'loris'})).then( + setupCharts(t, false, chartDetails, t('Total', {ns: 'loris'})).then( (data) => { setChartDetails(data); } @@ -147,7 +167,7 @@ const Recruitment = (props) => { title ='Recruitment' id ='statistics_recruitment' onChangeView ={(index) => { - setupCharts(false, chartDetails, t('Total', {ns: 'loris'})); + setupCharts(t, false, chartDetails, t('Total', {ns: 'loris'})); setShowFiltersBreakdown(false); }} views ={[ @@ -155,7 +175,7 @@ const Recruitment = (props) => { content: <>
- {progressBarBuilder(json['recruitment']['overall'])} + {progressBarBuilder(t, json['recruitment']['overall'])}

{showFilters('generalBreakdown')} @@ -211,7 +231,7 @@ const Recruitment = (props) => { ([key, value]) => { if (key !== 'overall') { return
- {progressBarBuilder(value)} + {progressBarBuilder(t, value)}
; } } @@ -237,7 +257,7 @@ const Recruitment = (props) => { .map( ([key, value]) => { return
- {progressBarBuilder(value)} + {progressBarBuilder(t, value)}
; } )} diff --git a/modules/statistics/jsx/widgets/studyprogression.js b/modules/statistics/jsx/widgets/studyprogression.js index 904e0b8e46a..3621f1a8897 100644 --- a/modules/statistics/jsx/widgets/studyprogression.js +++ b/modules/statistics/jsx/widgets/studyprogression.js @@ -74,6 +74,7 @@ const StudyProgression = (props) => { useEffect(() => { if (json && Object.keys(json).length !== 0) { setupCharts( + t, false, chartDetails, t('Total', {ns: 'loris'}) @@ -101,7 +102,7 @@ const StudyProgression = (props) => { title={t('Study Progression', {ns: 'statistics'})} id='statistics_studyprogression' onChangeView={(index) => { - setupCharts(false, chartDetails, t('Total', {ns: 'loris'})); + setupCharts(t, false, chartDetails, t('Total', {ns: 'loris'})); // reset filters when switching views setShowFiltersBreakdown(false); }} diff --git a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po index 169d066a9ff..26a4ab0ce02 100644 --- a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po @@ -57,3 +57,39 @@ msgstr "範囲開始" msgid "Range End" msgstr "範囲終了" + +msgid "Total recruitment by Age" +msgstr "年齢別総採用数" + +msgid "Ethnicity at Screening" +msgstr "スクリーニング時の民族" + +msgid "Total Recruitment per Site" +msgstr "サイトあたりの総採用数" + +msgid "Biological sex breakdown by site" +msgstr "部位別の生物学的性別の内訳" + +msgid "Candidate Age at Registration" +msgstr "登録時の候補者の年齢" + +msgid "Candidates registered" +msgstr "登録候補者" + +msgid "Target: {{target}}" +msgstr "ターゲット: {{target}}" + +msgid "Recruitment target of {{target}} was reached." +msgstr "{{target}} の採用目標に達しました。" + +msgid "Recruitment target of {{target}} was not reached." +msgstr "{{target}}の募集目標には達しませんでした" + +msgid "No target set" +msgstr "目標設定なし" + +msgid "{{total}} total participants." +msgstr "参加者総数は {{total}} 人です。" + +msgid "Overall Recruitment" +msgstr "全体的な採用" diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index 14ad546ffa2..703ba7c3c46 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -56,3 +56,39 @@ msgstr "" msgid "Range End" msgstr "" + +msgid "Total recruitment by Age" +msgstr "" + +msgid "Ethnicity at Screening" +msgstr "" + +msgid "Total Recruitment per Site" +msgstr "" + +msgid "Biological sex breakdown by site" +msgstr "" + +msgid "Candidate Age at Registration" +msgstr "" + +msgid "Candidates registered" +msgstr "" + +msgid "Target: {{target}}" +msgstr "" + +msgid "Recruitment target of {{target}} was reached." +msgstr "" + +msgid "Recruitment target of {{target}} was not reached." +msgstr "" + +msgid "No target set" +msgstr "" + +msgid "{{total}} total participants." +msgstr "" + +msgid "Overall Recruitment" +msgstr "" diff --git a/modules/statistics/php/widgets.class.inc b/modules/statistics/php/widgets.class.inc index 9f21b6d588a..56d307b0cf6 100644 --- a/modules/statistics/php/widgets.class.inc +++ b/modules/statistics/php/widgets.class.inc @@ -99,7 +99,7 @@ class Widgets extends \NDB_Page implements ETagCalculator $recruitment = [ 'overall' => $this->_createProjectProgressBar( 'overall', - "Overall Recruitment", + dgettext("statistics", "Overall Recruitment"), $recruitmentTarget, $total_participants, $recruitmentRaw, From df8dc805480755bd60710f2091905676459d01a2 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 21 Oct 2025 14:05:48 -0400 Subject: [PATCH 3/7] Add final translations of module --- modules/statistics/jsx/widgets/recruitment.js | 24 ++++++++++-------- .../locale/ja/LC_MESSAGES/statistics.po | 25 +++++++++++++++++++ modules/statistics/locale/statistics.pot | 24 ++++++++++++++++++ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/modules/statistics/jsx/widgets/recruitment.js b/modules/statistics/jsx/widgets/recruitment.js index 82d11e6910f..b6cf77f3fbf 100644 --- a/modules/statistics/jsx/widgets/recruitment.js +++ b/modules/statistics/jsx/widgets/recruitment.js @@ -161,10 +161,12 @@ const Recruitment = (props) => { [props.data] ); + const title = (subtitle) => t('Recruitment', {ns: 'statistics'}) + + ' — ' + t(subtitle, {ns: 'statistics'}); return loading ? : ( <> { setupCharts(t, false, chartDetails, t('Total', {ns: 'loris'})); @@ -189,9 +191,9 @@ const Recruitment = (props) => { ))} , - title: 'Recruitment - overall', - subtitle: `Total participants: ` - + json['recruitment']['overall']['total_recruitment'] || -1, + title: title('Overall'), + subtitle: t(`Total Participants: {{count}}`, {ns: 'statistics', count: + json['recruitment']['overall']['total_recruitment'] || -1}) }, { content: @@ -214,9 +216,9 @@ const Recruitment = (props) => { ) : (

There have been no candidates registered yet.

), - title: 'Recruitment - site breakdown', - subtitle: 'Total participants: ' - + json['recruitment']['overall']['total_recruitment'] || -1, + title: title('Site Breakdown'), + subtitle: t(`Total Participants: {{count}}`, {ns: 'statistics', count: + json['recruitment']['overall']['total_recruitment'] || -1}) }, { content: <> @@ -241,8 +243,8 @@ const Recruitment = (props) => { {showFilters('projectBreakdown')} {showChart('projectBreakdown', 'agedistribution_line')} , - title: 'Recruitment - project breakdown', - subtitle: `Projects: ${getTotalProjectsCount()}`, + title: title('Project Breakdown'), + subtitle: t(`Projects: {{count}}`, {ns: 'statistics', count: getTotalProjectsCount()}), }, { content: @@ -262,8 +264,8 @@ const Recruitment = (props) => { } )} , - title: 'Recruitment - cohort breakdown', - subtitle: `Cohorts: ${getTotalCohortsCount()}`, + title: title('Cohort Breakdown'), + subtitle: t(`Cohorts: {{count}}`, {ns: 'statistics', 'count': getTotalCohortsCount()}), }, ]} /> diff --git a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po index 26a4ab0ce02..4a1b26c8009 100644 --- a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po @@ -93,3 +93,28 @@ msgstr "参加者総数は {{total}} 人です。" msgid "Overall Recruitment" msgstr "全体的な採用" + +msgid "Recruitment" +msgstr "採用" + +msgid "Overall" +msgstr "全体" + +msgid "Site Breakdown" +msgstr "サイトの内訳" + +msgid "Project Breakdown" +msgstr "プロジェクトの内訳" + +msgid "Cohort Breakdown" +msgstr "コホートの内訳" + +msgid "Total Participants: {{count}}" +msgstr "参加者総数: {{count}}" + +msgid "Projects: {{count}}" +msgstr "プロジェクト: {{count}}" + +msgid "Cohorts: {{count}}" +msgstr "コホート: {{count}}" + diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index 703ba7c3c46..05a0e4efcdf 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -92,3 +92,27 @@ msgstr "" msgid "Overall Recruitment" msgstr "" + +msgid "Recruitment" +msgstr "" + +msgid "Overall" +msgstr "" + +msgid "Site Breakdown" +msgstr "" + +msgid "Project Breakdown" +msgstr "" + +msgid "Cohort Breakdown" +msgstr "" + +msgid "Total Participants: {{count}}" +msgstr "" + +msgid "Projects: {{count}}" +msgstr "" + +msgid "Cohorts: {{count}}" +msgstr "" From 47251524aea76ac80045929f9b2489b9cfe8d5db Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Tue, 21 Oct 2025 14:27:47 -0400 Subject: [PATCH 4/7] fix static errors --- .../jsx/widgets/helpers/progressbarBuilder.js | 35 ++++++++++++---- .../jsx/widgets/helpers/queryChartForm.js | 3 +- modules/statistics/jsx/widgets/recruitment.js | 42 +++++++++++++------ .../jsx/widgets/studyprogression.js | 2 +- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js b/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js index ce780240327..961b6420300 100644 --- a/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js +++ b/modules/statistics/jsx/widgets/helpers/progressbarBuilder.js @@ -1,7 +1,8 @@ /** * progressBarBuilder - generates the graph content. * - * @param {object} data - data needed to generate the graph content. + * @param {function} t - i18next React translation callback + * @param {object} data - data needed to generate the graph content. * @return {JSX.Element} the charts to render to the widget panel. */ const progressBarBuilder = (t, data) => { @@ -54,14 +55,23 @@ const progressBarBuilder = (t, data) => { }

- {t('Target: {{target}}', {target: data['recruitment_target'], ns: 'statistics'})} + {t( + 'Target: {{target}}', + {'target': data['recruitment_target'], 'ns': 'statistics'} + )}

{ data['recruitment_target'] && - {t('Recruitment target of {{target}} was reached.', { 'target': data['recruitment_target'], ns: 'statistics'})} - {' '}{t('{{total}} total participants.', { 'total': data['total_recruitment'], ns: 'statistics'})} + {t( + 'Recruitment target of {{target}} was reached.', + {'target': data['recruitment_target'], 'ns': 'statistics'} + )} + {' '}{t( + '{{total}} total participants.', + {'total': data['total_recruitment'], 'ns': 'statistics'} + )} } @@ -112,18 +122,27 @@ const progressBarBuilder = (t, data) => { { data['recruitment_target'] ?

- {t('Target: {{target}}', { 'target': data['recruitment_target'], ns: 'statistics'})} + {t( + 'Target: {{target}}', + {'target': data['recruitment_target'], 'ns': 'statistics'} + )}

:

- {t('No target set', {ns: 'statistics'})} + {t('No target set', {ns: 'statistics'})}

} { data['recruitment_target'] && - {t('Recruitment target of {{target}} was not reached.', { 'target': data['recruitment_target'], ns: 'statistics'})} - {' '}{t('{{total}} total participants.', { 'total': data['total_recruitment'], ns: 'statistics'})} + {t( + 'Recruitment target of {{target}} was not reached.', + {'target': data['recruitment_target'], 'ns': 'statistics'} + )} + {' '}{t( + '{{total}} total participants.', + {'total': data['total_recruitment'], 'ns': 'statistics'} + )} } diff --git a/modules/statistics/jsx/widgets/helpers/queryChartForm.js b/modules/statistics/jsx/widgets/helpers/queryChartForm.js index 8b7596eeefc..9d09cb0b6c1 100644 --- a/modules/statistics/jsx/widgets/helpers/queryChartForm.js +++ b/modules/statistics/jsx/widgets/helpers/queryChartForm.js @@ -203,7 +203,8 @@ const QueryChartForm = (props) => {
+ display: 'block'}}> + {t('Date Registered', {ns: 'statistics'})} { { 'generalBreakdown': { 'agerecruitment_pie': { - title: t('Total recruitment by Age', { ns: 'statistics' }), + title: t('Total recruitment by Age', {ns: 'statistics'}), filters: '', chartType: 'pie', dataType: 'pie', @@ -36,7 +36,7 @@ const Recruitment = (props) => { chartObject: null, }, 'ethnicity_pie': { - title: t('Ethnicity at Screening', {ns: 'statistics'}), + title: t('Ethnicity at Screening', {ns: 'statistics'}), filters: '', chartType: 'pie', dataType: 'pie', @@ -48,7 +48,7 @@ const Recruitment = (props) => { }, 'siteBreakdown': { 'siterecruitment_pie': { - title: t('Total Recruitment per Site', {ns: 'statistics'}), + title: t('Total Recruitment per Site', {ns: 'statistics'}), filters: '', chartType: 'pie', dataType: 'pie', @@ -58,7 +58,7 @@ const Recruitment = (props) => { chartObject: null, }, 'siterecruitment_bysex': { - title: t('Biological sex breakdown by site', {ns: 'statistics'}), + title: t('Biological sex breakdown by site', {ns: 'statistics'}), filters: '', chartType: 'bar', dataType: 'bar', @@ -87,7 +87,7 @@ const Recruitment = (props) => { // Re-set default state that depended on the translation let newdetails = {...chartDetails}; newdetails['generalBreakdown']['agerecruitment_pie']['title'] - = t('Total recruitment by Age', { ns: 'statistics' }); + = t('Total recruitment by Age', {ns: 'statistics'}); newdetails['generalBreakdown']['ethnicity_pie']['title'] = t('Ethnicity at Screening', {ns: 'statistics'}); newdetails['siteBreakdown']['siterecruitment_pie']['title'] @@ -111,7 +111,9 @@ const Recruitment = (props) => { className="btn btn-default btn-xs" onClick={() => setShowFiltersBreakdown((prev) => !prev)} > - {showFiltersBreakdown ? t('Hide Filters', {ns: 'loris'}) : t('Show Filters', {ns: 'loris'})} + {showFiltersBreakdown ? + t('Hide Filters', {ns: 'loris'}) + : t('Show Filters', {ns: 'loris'})}
{showFiltersBreakdown && ( @@ -192,8 +194,13 @@ const Recruitment = (props) => { , title: title('Overall'), - subtitle: t(`Total Participants: {{count}}`, {ns: 'statistics', count: - json['recruitment']['overall']['total_recruitment'] || -1}) + subtitle: t( + 'Total Participants: {{count}}', + { + ns: 'statistics', + count: json['recruitment']['overall']['total_recruitment'], + } + ), }, { content: @@ -217,8 +224,13 @@ const Recruitment = (props) => {

There have been no candidates registered yet.

), title: title('Site Breakdown'), - subtitle: t(`Total Participants: {{count}}`, {ns: 'statistics', count: - json['recruitment']['overall']['total_recruitment'] || -1}) + subtitle: t( + 'Total Participants: {{count}}', + { + ns: 'statistics', + count: json['recruitment']['overall']['total_recruitment'], + } + ), }, { content: <> @@ -244,7 +256,10 @@ const Recruitment = (props) => { {showChart('projectBreakdown', 'agedistribution_line')} , title: title('Project Breakdown'), - subtitle: t(`Projects: {{count}}`, {ns: 'statistics', count: getTotalProjectsCount()}), + subtitle: t( + 'Projects: {{count}}', + {ns: 'statistics', count: getTotalProjectsCount()} + ), }, { content: @@ -265,7 +280,10 @@ const Recruitment = (props) => { )} , title: title('Cohort Breakdown'), - subtitle: t(`Cohorts: {{count}}`, {ns: 'statistics', 'count': getTotalCohortsCount()}), + subtitle: t( + 'Cohorts: {{count}}', + {'ns': 'statistics', 'count': getTotalCohortsCount()} + ), }, ]} /> diff --git a/modules/statistics/jsx/widgets/studyprogression.js b/modules/statistics/jsx/widgets/studyprogression.js index 3621f1a8897..70d6962c1bc 100644 --- a/modules/statistics/jsx/widgets/studyprogression.js +++ b/modules/statistics/jsx/widgets/studyprogression.js @@ -74,7 +74,7 @@ const StudyProgression = (props) => { useEffect(() => { if (json && Object.keys(json).length !== 0) { setupCharts( - t, + t, false, chartDetails, t('Total', {ns: 'loris'}) From c75ca272ab16a7cf3443d177e979d38dddc93c92 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 22 Oct 2025 10:14:05 -0400 Subject: [PATCH 5/7] Add translation for line chart labels --- locale/ja/LC_MESSAGES/loris.po | 3 +++ locale/loris.pot | 3 +++ modules/statistics/jsx/widgets/recruitment.js | 10 ++++++++++ modules/statistics/locale/ja/LC_MESSAGES/statistics.po | 5 +++++ modules/statistics/locale/statistics.pot | 6 ++++++ 5 files changed, 27 insertions(+) diff --git a/locale/ja/LC_MESSAGES/loris.po b/locale/ja/LC_MESSAGES/loris.po index 1139010d25a..62b572436ee 100644 --- a/locale/ja/LC_MESSAGES/loris.po +++ b/locale/ja/LC_MESSAGES/loris.po @@ -232,6 +232,9 @@ msgstr "詳細フィルターを非表示" msgid "Language" msgstr "言語" +msgid "Ethnicity" +msgstr "民族" + # Data table strings msgid "{{pageCount}} rows displayed of {{totalCount}}." msgstr "{{totalCount}}行中{{pageCount}}行を表示" diff --git a/locale/loris.pot b/locale/loris.pot index 4af962e9d06..7c6971efb96 100644 --- a/locale/loris.pot +++ b/locale/loris.pot @@ -231,6 +231,9 @@ msgstr "" msgid "Language" msgstr "" +msgid "Ethnicity" +msgstr "" + # Data table strings msgid "{{pageCount}} rows displayed of {{totalCount}}." msgstr "" diff --git a/modules/statistics/jsx/widgets/recruitment.js b/modules/statistics/jsx/widgets/recruitment.js index 7378a857416..77812611baa 100644 --- a/modules/statistics/jsx/widgets/recruitment.js +++ b/modules/statistics/jsx/widgets/recruitment.js @@ -88,12 +88,22 @@ const Recruitment = (props) => { let newdetails = {...chartDetails}; newdetails['generalBreakdown']['agerecruitment_pie']['title'] = t('Total recruitment by Age', {ns: 'statistics'}); + newdetails['generalBreakdown']['agerecruitment_pie']['label'] + = t('Age (Years)', {ns: 'statistics'}); + newdetails['generalBreakdown']['ethnicity_pie']['title'] = t('Ethnicity at Screening', {ns: 'statistics'}); + newdetails['generalBreakdown']['ethnicity_pie']['label'] + = t('Ethnicity', {ns: 'loris'}); + newdetails['siteBreakdown']['siterecruitment_pie']['title'] = t('Total Recruitment per Site', {ns: 'statistics'}); + newdetails['siteBreakdown']['siterecruitment_pie']['label'] + = t('Participants', {ns: 'statistics'}); + newdetails['siteBreakdown']['siterecruitment_bysex']['title'] = t('Biological sex breakdown by site', {ns: 'statistics'}); + newdetails['projectBreakdown']['agedistribution_line']['title'] = t('Candidate Age at Registration', {ns: 'statistics'}); setChartDetails(newdetails); diff --git a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po index 4a1b26c8009..e089d6b132a 100644 --- a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po @@ -118,3 +118,8 @@ msgstr "プロジェクト: {{count}}" msgid "Cohorts: {{count}}" msgstr "コホート: {{count}}" +msgid "Age (Years)" +msgstr "年齢(歳)" + +msgid "Participants" +msgstr "参加者" diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index 05a0e4efcdf..aea3c67e26b 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -116,3 +116,9 @@ msgstr "" msgid "Cohorts: {{count}}" msgstr "" + +msgid "Age (Years)" +msgstr "" + +msgid "Participants" +msgstr "" From 66026d06456707064ad88aed5a404a4378ff0f68 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 22 Oct 2025 10:45:17 -0400 Subject: [PATCH 6/7] Translate loading and unknown --- locale/ja/LC_MESSAGES/loris.po | 5 +++++ locale/loris.pot | 5 +++++ modules/statistics/jsx/WidgetIndex.js | 2 +- modules/statistics/jsx/widgets/helpers/chartBuilder.js | 5 +++-- modules/statistics/locale/ja/LC_MESSAGES/statistics.po | 3 +++ modules/statistics/locale/statistics.pot | 3 +++ modules/statistics/php/charts.class.inc | 2 +- 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/locale/ja/LC_MESSAGES/loris.po b/locale/ja/LC_MESSAGES/loris.po index 62b572436ee..172c17a893d 100644 --- a/locale/ja/LC_MESSAGES/loris.po +++ b/locale/ja/LC_MESSAGES/loris.po @@ -335,3 +335,8 @@ msgstr "{{months}}ヶ月" msgid "{{years}} years old" msgstr "{{years}}歳" + +# Other generic terms +msgid "Loading..." +msgstr "読み込み中..." + diff --git a/locale/loris.pot b/locale/loris.pot index 7c6971efb96..917ba1e5fc0 100644 --- a/locale/loris.pot +++ b/locale/loris.pot @@ -334,3 +334,8 @@ msgstr "" msgid "{{years}} years old" msgstr "" + +# Other generic terms +msgid "Loading..." +msgstr "" + diff --git a/modules/statistics/jsx/WidgetIndex.js b/modules/statistics/jsx/WidgetIndex.js index fed2202246c..3839ae04916 100644 --- a/modules/statistics/jsx/WidgetIndex.js +++ b/modules/statistics/jsx/WidgetIndex.js @@ -187,7 +187,7 @@ const WidgetIndex = (props) => { setChartDetails ) => { // Unload all charts in the section first - unloadCharts(chartDetails, section); + unloadCharts(t, chartDetails, section); // Clear cached data from chartDetails to prevent old data from showing let clearedChartDetails = {...chartDetails}; diff --git a/modules/statistics/jsx/widgets/helpers/chartBuilder.js b/modules/statistics/jsx/widgets/helpers/chartBuilder.js index dc0764a495b..b3b51d1f2c3 100644 --- a/modules/statistics/jsx/widgets/helpers/chartBuilder.js +++ b/modules/statistics/jsx/widgets/helpers/chartBuilder.js @@ -254,10 +254,11 @@ const getChartData = async (target, filters) => { /** * unloadCharts - unload all charts in a section to clear their data + * @param {t} The i18next translation callback * @param {object} chartDetails * @param {string} section */ -const unloadCharts = (chartDetails, section) => { +const unloadCharts = (t, chartDetails, section) => { Object.keys(chartDetails[section]).forEach((chartID) => { const chart = chartDetails[section][chartID].chartObject; if (chart && typeof chart.unload === 'function') { @@ -266,7 +267,7 @@ const unloadCharts = (chartDetails, section) => { // Clear the chart container completely const element = document.getElementById(chartID); if (element) { - element.innerHTML ='

Loading...

'; + element.innerHTML ='

' + t('Loading...', {ns: 'loris'}) + '

'; } }); }; diff --git a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po index e089d6b132a..27f16d30052 100644 --- a/modules/statistics/locale/ja/LC_MESSAGES/statistics.po +++ b/modules/statistics/locale/ja/LC_MESSAGES/statistics.po @@ -123,3 +123,6 @@ msgstr "年齢(歳)" msgid "Participants" msgstr "参加者" + +msgid "Unknown" +msgstr "未知" diff --git a/modules/statistics/locale/statistics.pot b/modules/statistics/locale/statistics.pot index aea3c67e26b..f0ea8b7e9f1 100644 --- a/modules/statistics/locale/statistics.pot +++ b/modules/statistics/locale/statistics.pot @@ -122,3 +122,6 @@ msgstr "" msgid "Participants" msgstr "" + +msgid "Unknown" +msgstr "" diff --git a/modules/statistics/php/charts.class.inc b/modules/statistics/php/charts.class.inc index 1ac7839ada7..a9d39d04cb1 100644 --- a/modules/statistics/php/charts.class.inc +++ b/modules/statistics/php/charts.class.inc @@ -425,7 +425,7 @@ class Charts extends \NDB_Page $id = strtolower($id); $id = ucwords($id); if ($id == null) { - $id = "Unknown"; + $id = dgettext("statistics", "Unknown"); } $label = $id; $recruitmentByEthnicityData[] = ["label" => $label, "total" => $count]; From e9c394377921734b4821bf5e8c0726cafcb562b1 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 22 Oct 2025 10:50:55 -0400 Subject: [PATCH 7/7] Fix hyphen in test --- modules/dashboard/test/DashboardTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/dashboard/test/DashboardTest.php b/modules/dashboard/test/DashboardTest.php index dff0dbef6f5..97a8867eda8 100644 --- a/modules/dashboard/test/DashboardTest.php +++ b/modules/dashboard/test/DashboardTest.php @@ -409,17 +409,17 @@ public function testDashboardRecruitmentView() ) )->getText(); - $this->assertStringContainsString("Recruitment - overall", $assertText1); + $this->assertStringContainsString("Recruitment — Overall", $assertText1); $this->assertStringContainsString( - "Recruitment - site breakdown", + "Recruitment — Site Breakdown", $assertText2 ); $this->assertStringContainsString( - "Recruitment - project breakdown", + "Recruitment — Project Breakdown", $assertText3 ); $this->assertStringContainsString( - "Recruitment - cohort breakdown", + "Recruitment — Cohort Breakdown", $assertText4 ); }