From ef494b3e816a1817e9c0384ded8d17a8f2c23b23 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 22 Sep 2025 13:00:29 -0400 Subject: [PATCH 1/3] [candidate_profile] Translate Candidate Info widget on candidate_profile page This translates the first candidate info widget on the candidatE_profile page. Japanese is used as the sample language. --- Makefile | 8 +- jsx/I18nSetup.js | 4 + locale/en/LC_MESSAGES/loris.po | 255 ++++++++++++++++++ locale/ja/LC_MESSAGES/loris.po | 20 +- locale/loris.pot | 21 +- .../candidate_list/jsx/candidateListIndex.js | 4 +- .../ja/LC_MESSAGES/candidate_parameters.po | 2 + .../candidate_parameters/php/module.class.inc | 10 +- .../candidate_profile/jsx/CandidateInfo.js | 30 ++- .../candidate_profile/php/module.class.inc | 2 +- 10 files changed, 323 insertions(+), 33 deletions(-) create mode 100644 locale/en/LC_MESSAGES/loris.po diff --git a/Makefile b/Makefile index 24064254445..b283828474f 100755 --- a/Makefile +++ b/Makefile @@ -63,12 +63,14 @@ testdata: php tools/raisinbread_refresh.php locales: + msgfmt -o locale/en/LC_MESSAGES/loris.mo locale/en/LC_MESSAGES/loris.po + npx i18next-conv -l en -s locale/en/LC_MESSAGES/loris.po -t locale/en/LC_MESSAGES/loris.json --compatibilityJSON v4 msgfmt -o locale/ja/LC_MESSAGES/loris.mo locale/ja/LC_MESSAGES/loris.po - npx i18next-conv -l ja -s locale/ja/LC_MESSAGES/loris.po -t locale/ja/LC_MESSAGES/loris.json + npx i18next-conv -l ja -s locale/ja/LC_MESSAGES/loris.po -t locale/ja/LC_MESSAGES/loris.json --compatibilityJSON v4 msgfmt -o locale/hi/LC_MESSAGES/loris.mo locale/hi/LC_MESSAGES/loris.po - npx i18next-conv -l hi -s locale/hi/LC_MESSAGES/loris.po -t locale/hi/LC_MESSAGES/loris.json + npx i18next-conv -l hi -s locale/hi/LC_MESSAGES/loris.po -t locale/hi/LC_MESSAGES/loris.json --compatibilityJSON v4 msgfmt -o locale/es/LC_MESSAGES/loris.mo locale/es/LC_MESSAGES/loris.po - npx i18next-conv -l es -s locale/es/LC_MESSAGES/loris.po -t locale/es/LC_MESSAGES/loris.json + npx i18next-conv -l es -s locale/es/LC_MESSAGES/loris.po -t locale/es/LC_MESSAGES/loris.json --compatibilityJSON v4 msgfmt -o modules/new_profile/locale/ja/LC_MESSAGES/new_profile.mo modules/new_profile/locale/ja/LC_MESSAGES/new_profile.po npx i18next-conv -l ja -s modules/new_profile/locale/ja/LC_MESSAGES/new_profile.po -t modules/new_profile/locale/ja/LC_MESSAGES/new_profile.json msgfmt -o modules/new_profile/locale/hi/LC_MESSAGES/new_profile.mo modules/new_profile/locale/hi/LC_MESSAGES/new_profile.po diff --git a/jsx/I18nSetup.js b/jsx/I18nSetup.js index eb2b382fdf0..7cbf9a3c854 100644 --- a/jsx/I18nSetup.js +++ b/jsx/I18nSetup.js @@ -4,6 +4,7 @@ import {initReactI18next} from 'react-i18next'; import jaResources from '../locale/ja/LC_MESSAGES/loris.json'; import hiResources from '../locale/hi/LC_MESSAGES/loris.json'; import esResources from '../locale/es/LC_MESSAGES/loris.json'; +import enResources from '../locale/en/LC_MESSAGES/loris.json'; const resources = { ja: { @@ -15,6 +16,9 @@ const resources = { es: { loris: esResources, }, + en: { + loris: enResources, + } }; i18n diff --git a/locale/en/LC_MESSAGES/loris.po b/locale/en/LC_MESSAGES/loris.po new file mode 100644 index 00000000000..02f9fc747bc --- /dev/null +++ b/locale/en/LC_MESSAGES/loris.po @@ -0,0 +1,255 @@ +# Default LORIS strings to be translated (English). +# Copy this to a language specific file and add translations to the +# new file. +# Copyright (C) 2025 +# This file is distributed under the same license as the LORIS package. +# Dave MacFarlane , 2025. +# + +msgid "" +msgstr "" +"Project-Id-Version: LORIS 27\n" +"Report-Msgid-Bugs-To: https://github.com/aces/Loris/issues\n" +"POT-Creation-Date: 2025-04-08 14:37-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +# Smarty template main.tpl strings +msgid "LORIS" +msgstr "LORIS" + +msgid "DEV" +msgstr "DEV" + +msgid "Affiliations" +msgstr "Affiliations" + +msgid "Site Affiliation" +msgstr "Site Affiliation" + +msgid "Site Affiliations" +msgstr "Site Affiliations" + +msgid "Project Affiliation" +msgstr "Project Affiliation" + +msgid "Project Affiliations" +msgstr "Project Affiliations" + +msgid "Log Out" +msgstr "Log Out" + +msgid "My Preferences" +msgstr "My Preferences" + +# Auto extracted strings from PHP +# Menu categories +#: modules/configuration/php/module.class.inc:50 +#: modules/help_editor/php/module.class.inc:47 +#: modules/instrument_manager/php/module.class.inc:53 +#: modules/module_manager/php/module.class.inc:50 +#: modules/server_processes_manager/php/module.class.inc:104 +#: modules/survey_accounts/php/module.class.inc:48 +#: modules/user_accounts/php/module.class.inc:53 +msgid "Admin" +msgstr "Admin" +#: modules/acknowledgements/php/module.class.inc:54 +#: modules/api_docs/php/module.class.inc:66 +#: modules/battery_manager/php/module.class.inc:55 +#: modules/datadict/php/module.class.inc:49 +#: modules/data_release/php/module.class.inc:56 +#: modules/dictionary/php/module.class.inc:49 +msgid "Data Dictionary" +msgstr "Data Dictionary" + +#: modules/document_repository/php/module.class.inc:56 +#: modules/instrument_builder/php/module.class.inc:48 +#: modules/issue_tracker/php/module.class.inc:54 +#: modules/schedule_module/php/module.class.inc:49 +msgid "Tools" +msgstr "Tools" + +#: modules/behavioural_qc/php/module.class.inc:51 +#: modules/conflict_resolver/php/module.class.inc:83 +#: modules/examiner/php/module.class.inc:50 +#: modules/media/php/module.class.inc:33 +msgid "Clinical" +msgstr "Clinical" + +#: modules/dicom_archive/php/module.class.inc:115 +#: modules/imaging_browser/php/module.class.inc:40 +#: modules/imaging_qc/php/module.class.inc:50 +#: modules/imaging_uploader/php/module.class.inc:54 +#: modules/mri_violations/php/module.class.inc:53 +msgid "Imaging" +msgstr "Imaging" + +#: modules/candidate_list/php/module.class.inc:76 +#: modules/new_profile/php/module.class.inc:50 +msgid "Candidate" +msgstr "Candidate" + +#: modules/genomic_browser/php/module.class.inc:53 +msgid "Genomics" +msgstr "Genomics" + +#: modules/electrophysiology_browser/php/module.class.inc:54 +msgid "Electrophysiology" +msgstr "Electrophysiology" + +#: modules/dataquery/php/module.class.inc:33 +#: modules/dqt/php/module.class.inc:49 +#: modules/publication/php/module.class.inc:55 +#: modules/statistics/php/module.class.inc:49 +msgid "Reports" +msgstr "Reports" + +# Common loris terms. Consider moving these to their own textdomain? +msgid "TimePoint" +msgstr "TimePoint" + +# Common select option labels +msgid "Yes" +msgstr "Yes" + +msgid "No" +msgstr "No" + +msgid "Cancel" +msgstr "Cancel" + +# Common candidate terms +msgid "PSCID" +msgstr "PSCID" + +msgid "DCCID" +msgstr "DCCID" + +msgid "Visit Label" +msgstr "Visit Label" + +msgid "Site" +msgstr "Site" + +msgid "Module" +msgstr "Module" + +msgid "Project" +msgstr "Project" + +msgid "Cohort" +msgid_plural "Cohorts" +msgstr[0] "Cohort" +msgstr[1] "Cohorts" + +msgid "Session" +msgstr "Session" + +msgid "Date of registration" +msgstr "Date of registration" + +msgid "Participant Status" +msgstr "Participant Status" + +msgid "DoB" +msgstr "DoB" + +msgid "Sex" +msgstr "Sex" + +msgid "Feedback" +msgstr "Feedback" + +msgid "Scans" +msgstr "Scans" + +msgid "Stage" +msgstr "Stage" + +msgid "Sent To DCC" +msgstr "Sent To DCC" + +msgid "Date of Birth" +msgstr "Date of Birth" + +msgid "Visits" +msgstr "Visits" + +msgid "An error occured while loading the page." +msgstr "An error occured while loading the page." + +msgid "Entity Type" +msgstr "Entity Type" + +msgid "Scan Done" +msgstr "Scan Done" + +msgid "Show Advanced Filters" +msgstr "Show Advanced Fitlers" + +msgid "Hide Advanced Filters" +msgstr "Hide Advanced Filters" + +# Data table strings +msgid "{{pageCount}} rows displayed of {{totalCount}}." +msgstr "{{pageCount}} rows displayed of {{totalCount}}." + +msgid "Maximum rows per page:" +msgstr "Maximum rows per page:" + +msgid "Download Data as CSV" +msgstr "Download Data as CSV" + +# Dashboard panel strings +msgid "Views" +msgstr "Views" + +# Common strings on widgets +msgid "NEW" +msgstr "NEW" + +msgid "Updated" +msgstr "Updated" + +msgid "Uploaded" +msgstr "Uploaded" + +msgid "Total" +msgstr "Total" + +# Common timepoint and vist terms +msgid "Not Started" +msgstr "Not Started" + +msgid "Screening" +msgstr "Screening" + +msgid "Visit" +msgstr "Visit" + +msgid "Approval" +msgstr "Approval" + +msgid "Subject" +msgstr "Subject" + +msgid "Recycling Bin" +msgstr "Recycling Bin" + +msgid "Pass" +msgstr "Pass" + +msgid "Failure" +msgstr "Failure" + +msgid "Withdrawal" +msgstr "Withdrawal" + +msgid "In Progress" +msgstr "In Progress" diff --git a/locale/ja/LC_MESSAGES/loris.po b/locale/ja/LC_MESSAGES/loris.po index 128419aa65d..f8d8e730436 100644 --- a/locale/ja/LC_MESSAGES/loris.po +++ b/locale/ja/LC_MESSAGES/loris.po @@ -17,6 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" # Smarty template main.tpl strings msgid "LORIS" @@ -125,6 +126,9 @@ msgstr "作成する" msgid "Cancel" msgstr "取り消す" +msgid "N/A" +msgstr "適用できない" + msgid "Success!" msgstr "成功!" @@ -161,10 +165,8 @@ msgid "Project" msgstr "プロジェクト" msgid "Cohort" -msgstr "コホート" - -msgid "Cohorts" -msgstr "コホート" +msgid_plural "Cohorts" +msgstr[0] "コホート" msgid "Session" msgstr "セッション" @@ -277,3 +279,13 @@ msgstr "撤退" msgid "In Progress" msgstr "進行中" + +# Age related terms +msgid "Age" +msgstr "年" + +msgid "{{months}} months old" +msgstr "{{months}}ヶ月" + +msgid "{{years}} years old" +msgstr "{{years}}歳" diff --git a/locale/loris.pot b/locale/loris.pot index e39c09b8359..3447c50ba5c 100644 --- a/locale/loris.pot +++ b/locale/loris.pot @@ -122,6 +122,9 @@ msgstr "" msgid "Create" msgstr "" +msgid "N/A" +msgstr "" + # Common swal labels msgid "Cancel" msgstr "" @@ -149,11 +152,9 @@ msgid "Project" msgstr "" msgid "Cohort" -msgstr "" - -msgid "Cohorts" -msgstr "" - +msgid_plural "Cohorts" +msgstr[0] "" + msgid "Session" msgstr "" @@ -265,3 +266,13 @@ msgstr "" msgid "In Progress" msgstr "" + +# Age related terms +msgid "Age" +msgstr "" + +msgid "{{months}} months old" +msgstr "" + +msgid "{{years}} years old" +msgstr "" diff --git a/modules/candidate_list/jsx/candidateListIndex.js b/modules/candidate_list/jsx/candidateListIndex.js index bb07743286f..129edb06a97 100644 --- a/modules/candidate_list/jsx/candidateListIndex.js +++ b/modules/candidate_list/jsx/candidateListIndex.js @@ -163,7 +163,7 @@ class CandidateListIndex extends Component { ); } - if (column === this.props.t('Cohort', {ns: 'loris'})) { + if (column === this.props.t('Cohort', {ns: 'loris', count: 1})) { let result = (cell) ? {cell} : ; return result; } @@ -230,7 +230,7 @@ class CandidateListIndex extends Component { }, }, { - 'label': this.props.t('Cohort', {ns: 'loris'}), + 'label': this.props.t('Cohort', {ns: 'loris', count: 1}), 'show': true, 'filter': { name: 'cohort', diff --git a/modules/candidate_parameters/locale/ja/LC_MESSAGES/candidate_parameters.po b/modules/candidate_parameters/locale/ja/LC_MESSAGES/candidate_parameters.po index bbcc033ed2a..6d273f5b391 100644 --- a/modules/candidate_parameters/locale/ja/LC_MESSAGES/candidate_parameters.po +++ b/modules/candidate_parameters/locale/ja/LC_MESSAGES/candidate_parameters.po @@ -21,3 +21,5 @@ msgstr "" msgid "Candidate Parameters" msgstr "候補パラメータ" +msgid "Caveat" +msgstr "警告" diff --git a/modules/candidate_parameters/php/module.class.inc b/modules/candidate_parameters/php/module.class.inc index aec44710b36..dc3a291a8c0 100644 --- a/modules/candidate_parameters/php/module.class.inc +++ b/modules/candidate_parameters/php/module.class.inc @@ -116,8 +116,9 @@ class Module extends \Module // Participant Status $entries = [ new CandidateInfo( - "Participant Status", - $candidate->getParticipantStatusDescription($db) ?: 'N/A', + dgettext("loris", "Participant Status"), + $candidate->getParticipantStatusDescription($db) + ?: dgettext("loris", 'N/A'), ), ]; @@ -164,10 +165,11 @@ class Module extends \Module return null; } + $title = dgettext("candidate_parameters", "Caveat"); if ($caveat['Description'] == 'Other') { - return new CandidateInfo("Caveat", $caveat['flagged_other']); + return new CandidateInfo($title, $caveat['flagged_other']); } - return new CandidateInfo("Caveat", $caveat['Description']); + return new CandidateInfo($title, $caveat['Description']); } /** diff --git a/modules/candidate_profile/jsx/CandidateInfo.js b/modules/candidate_profile/jsx/CandidateInfo.js index 831193da45a..be9ca8754ea 100644 --- a/modules/candidate_profile/jsx/CandidateInfo.js +++ b/modules/candidate_profile/jsx/CandidateInfo.js @@ -1,11 +1,13 @@ import React, {Component} from 'react'; +import i18n from 'I18nSetup'; +import {withTranslation} from 'react-i18next'; import PropTypes from 'prop-types'; /** * CandidateInfo is a React component which is used for the * CandidateInfo table providing an overview of the candidate. */ -export class CandidateInfo extends Component { +class CandidateInfo extends Component { /** * Construct the object. * @@ -35,9 +37,9 @@ export class CandidateInfo extends Component { const months = years*12 + now.getMonth() - dobdate.getMonth(); if (months <= 36) { - return months + ' months old'; + return this.props.t('{{months}} months old', {ns: 'loris', months: months}); } - return years + ' years old'; + return this.props.t('{{years}} years old', {ns: 'loris', years: years}); } /** @@ -105,45 +107,43 @@ export class CandidateInfo extends Component { */ render() { const cohorts = this.getCohorts(this.props.Visits); - const subprojlabel = cohorts.length == 1 ? 'Cohort' - : 'Cohorts'; const data = [ { - label: 'PSCID', + label: this.props.t('PSCID', {ns: 'loris'}), value: this.props.Candidate.Meta.PSCID, }, { - label: 'DCCID', + label: this.props.t('DCCID', {ns: 'loris'}), value: this.props.Candidate.Meta.CandID, }, { - label: 'Date of Birth', + label: this.props.t('Date of Birth', {ns: 'loris'}), value: this.props.Candidate.Meta.DoB, valueWhitespace: 'nowrap', }, { - label: 'Age', + label: this.props.t('Age', {ns: 'loris'}), value: this.calcAge(this.props.Candidate.Meta.DoB), }, { - label: 'Sex', + label: this.props.t('Sex', {ns: 'loris'}), value: this.props.Candidate.Meta.Sex, }, { - label: 'Project', + label: this.props.t('Project', {ns: 'loris'}), value: this.props.Candidate.Meta.Project, }, { - label: subprojlabel, + label: this.props.t('Cohort', {ns: 'loris', count: cohorts.length}), value: cohorts.join(', '), }, { - label: 'Site', + label: this.props.t('Site', {ns: 'loris'}), value: this.props.Candidate.Meta.Site, }, { - label: 'Visits', + label: this.props.t('Visits', {ns: 'loris'}), value: this.getVisitList(this.props.Visits), width: '12em', }, @@ -211,3 +211,5 @@ CandidateInfo.propTypes = { VisitMap: PropTypes.object.isRequired, ExtraCandidateInfo: PropTypes.array, }; + +export default {CandidateInfo: withTranslation(['candidate_profile', 'loris'])(CandidateInfo)}; diff --git a/modules/candidate_profile/php/module.class.inc b/modules/candidate_profile/php/module.class.inc index d7e27368f2a..831829417d6 100644 --- a/modules/candidate_profile/php/module.class.inc +++ b/modules/candidate_profile/php/module.class.inc @@ -92,7 +92,7 @@ class Module extends \Module new CandidateWidget( dgettext("candidate_profile", "Candidate Info"), $baseurl . "/candidate_profile/js/CandidateInfo.js", - "lorisjs.candidate_profile.CandidateInfo.CandidateInfo", + "lorisjs.candidate_profile.CandidateInfo.default.CandidateInfo", ['ExtraCandidateInfo' => $params], 1, 2, From fe3885411060376cd9aaa98875cc936a2dcba600 Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Mon, 22 Sep 2025 14:11:27 -0400 Subject: [PATCH 2/3] Format date according to locale --- jsx/I18nSetup.js | 2 +- .../candidate_profile/jsx/CandidateInfo.js | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/jsx/I18nSetup.js b/jsx/I18nSetup.js index 7cbf9a3c854..35bc3f18668 100644 --- a/jsx/I18nSetup.js +++ b/jsx/I18nSetup.js @@ -18,7 +18,7 @@ const resources = { }, en: { loris: enResources, - } + }, }; i18n diff --git a/modules/candidate_profile/jsx/CandidateInfo.js b/modules/candidate_profile/jsx/CandidateInfo.js index be9ca8754ea..0cddef876d4 100644 --- a/modules/candidate_profile/jsx/CandidateInfo.js +++ b/modules/candidate_profile/jsx/CandidateInfo.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; -import i18n from 'I18nSetup'; import {withTranslation} from 'react-i18next'; import PropTypes from 'prop-types'; +import 'I18nSetup'; /** * CandidateInfo is a React component which is used for the @@ -37,9 +37,15 @@ class CandidateInfo extends Component { const months = years*12 + now.getMonth() - dobdate.getMonth(); if (months <= 36) { - return this.props.t('{{months}} months old', {ns: 'loris', months: months}); + return this.props.t( + '{{months}} months old', + {ns: 'loris', months: months} + ); } - return this.props.t('{{years}} years old', {ns: 'loris', years: years}); + return this.props.t( + '{{years}} years old', + {ns: 'loris', years: years} + ); } /** @@ -107,6 +113,14 @@ class CandidateInfo extends Component { */ render() { const cohorts = this.getCohorts(this.props.Visits); + const dateFormatter = new Intl.DateTimeFormat( + loris.user.langpref.replace('_', '-'), + { + style: 'short', + timeZone: 'UTC', + + } + ); const data = [ { @@ -119,7 +133,7 @@ class CandidateInfo extends Component { }, { label: this.props.t('Date of Birth', {ns: 'loris'}), - value: this.props.Candidate.Meta.DoB, + value: dateFormatter.format(new Date(this.props.Candidate.Meta.DoB)), valueWhitespace: 'nowrap', }, { @@ -210,6 +224,11 @@ CandidateInfo.propTypes = { Visits: PropTypes.array.isRequired, VisitMap: PropTypes.object.isRequired, ExtraCandidateInfo: PropTypes.array, + + // Provided by withTranslation HOC + t: PropTypes.func, }; -export default {CandidateInfo: withTranslation(['candidate_profile', 'loris'])(CandidateInfo)}; +export default { + CandidateInfo: withTranslation(['candidate_profile', 'loris'])(CandidateInfo), +}; From 614a5c3088c0f4d0bf898e40642c1c7aeeec4b9f Mon Sep 17 00:00:00 2001 From: Dave MacFarlane Date: Wed, 15 Oct 2025 14:02:27 -0400 Subject: [PATCH 3/3] Add to pot file --- modules/candidate_parameters/locale/candidate_parameters.pot | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/candidate_parameters/locale/candidate_parameters.pot b/modules/candidate_parameters/locale/candidate_parameters.pot index b9858f19dbf..214fa6dfec4 100644 --- a/modules/candidate_parameters/locale/candidate_parameters.pot +++ b/modules/candidate_parameters/locale/candidate_parameters.pot @@ -21,3 +21,5 @@ msgstr "" msgid "Candidate Parameters" msgstr "" +msgid "Caveat" +msgstr ""