diff --git a/cypress/e2e/ScopeSelector/ScopeSelector.js b/cypress/e2e/ScopeSelector/ScopeSelector.js index b3298a75e6..90e7a8e300 100644 --- a/cypress/e2e/ScopeSelector/ScopeSelector.js +++ b/cypress/e2e/ScopeSelector/ScopeSelector.js @@ -284,7 +284,7 @@ And('you see the enrollment event Edit page but there is no org unit id in the u And('you see the enrollment event New page but there is no stage id in the url', () => { cy.url().should('eq', `${Cypress.config().baseUrl}/#/enrollmentEventNew?enrollmentId=Aemr3Q02aqV&orgUnitId=DiszpKrYNg8&programId=ur1Edk5Oe2n&teiId=eUTmQGull6H`); - cy.contains('Choose a stage for a new event'); + cy.contains('Choose a program stage for a new event'); }); And('you see the enrollment page without org unit in the url', () => { diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollmentNote/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollmentNote/index.js index 67f9b5df5f..e96b83be28 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollmentNote/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollmentNote/index.js @@ -3,7 +3,7 @@ import { When, Then } from '@badeball/cypress-cucumber-preprocessor'; const timeStamp = Math.round((new Date()).getTime() / 1000); Then('the stages and events should be loaded', () => { - cy.contains('Stages and Events').should('exist'); + cy.contains('Program stages and events').should('exist'); }); When(/^you fill in the note: (.*)$/, (note) => { diff --git a/i18n/en.pot b/i18n/en.pot index c79ff5717a..61f6a6d9fa 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-08-17T07:54:36.289Z\n" -"PO-Revision-Date: 2026-08-17T07:54:36.289Z\n" +"POT-Creation-Date: 2026-08-28T14:48:19.309Z\n" +"PO-Revision-Date: 2026-08-28T14:48:19.309Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." @@ -42,20 +42,20 @@ msgstr "Bulk data entry" msgid "Program overview" msgstr "Program overview" -msgid "Active enrollments" -msgstr "Active enrollments" +msgid "Active {{enrollmentsLabel}}" +msgstr "Active {{enrollmentsLabel}}" -msgid "Completed enrollments" -msgstr "Completed enrollments" +msgid "Completed {{enrollmentsLabel}}" +msgstr "Completed {{enrollmentsLabel}}" -msgid "Cancelled enrollments" -msgstr "Cancelled enrollments" +msgid "Cancelled {{enrollmentsLabel}}" +msgstr "Cancelled {{enrollmentsLabel}}" msgid "Search" msgstr "Search" -msgid "Enrollment dashboard" -msgstr "Enrollment dashboard" +msgid "{{enrollmentLabel}} dashboard" +msgstr "{{enrollmentLabel}} dashboard" msgid "View event" msgstr "View event" @@ -81,8 +81,8 @@ msgstr "View {{programName}} dashboard" msgid "View dashboard" msgstr "View dashboard" -msgid "View active enrollment" -msgstr "View active enrollment" +msgid "View active {{enrollmentLabel}}" +msgstr "View active {{enrollmentLabel}}" msgid "Re-enroll in {{programName}}" msgstr "Re-enroll in {{programName}}" @@ -99,8 +99,8 @@ msgstr "Previously enrolled" msgid "Organisation unit" msgstr "Organisation unit" -msgid "Date of enrollment" -msgstr "Date of enrollment" +msgid "Date of {{enrollmentLabel}}" +msgstr "Date of {{enrollmentLabel}}" msgid "Last updated" msgstr "Last updated" @@ -141,9 +141,6 @@ msgstr "Area" msgid "Coordinate" msgstr "Coordinate" -msgid "Enrollment" -msgstr "Enrollment" - msgid "Complete event" msgstr "Complete event" @@ -175,8 +172,8 @@ msgstr "Please select {{categoryName}}" msgid "A date in the future is not allowed" msgstr "A date in the future is not allowed" -msgid "Saving a new enrollment in {{programName}} in {{orgUnitName}}." -msgstr "Saving a new enrollment in {{programName}} in {{orgUnitName}}." +msgid "Saving a new {{enrollmentLabel}} in {{programName}} in {{orgUnitName}}." +msgstr "Saving a new {{enrollmentLabel}} in {{programName}} in {{orgUnitName}}." msgid "Saving a {{trackedEntityName}} in {{programName}} in {{orgUnitName}}." msgstr "Saving a {{trackedEntityName}} in {{programName}} in {{orgUnitName}}." @@ -318,9 +315,6 @@ msgstr "Saving a {{trackedEntityName}}" msgid "without" msgstr "without" -msgid "enrollment" -msgstr "enrollment" - msgid "in" msgstr "in" @@ -333,25 +327,29 @@ msgstr "An error has occurred. See log for details" msgid "{{programStageName}} completed" msgstr "{{programStageName}} completed" -msgid "Would you like to complete the enrollment and all active events as well?" -msgstr "Would you like to complete the enrollment and all active events as well?" +msgid "" +"Would you like to complete the {{enrollmentLabel}} and all active events as " +"well?" +msgstr "" +"Would you like to complete the {{enrollmentLabel}} and all active events as " +"well?" msgid "{{count}} event in {{programStageName}}" msgid_plural "{{count}} event in {{programStageName}}" msgstr[0] "{{count}} event in {{programStageName}}" msgstr[1] "{{count}} events in {{programStageName}}" -msgid "Yes, complete enrollment and events" -msgstr "Yes, complete enrollment and events" +msgid "Yes, complete {{enrollmentLabel}} and events" +msgstr "Yes, complete {{enrollmentLabel}} and events" -msgid "Complete enrollment only" -msgstr "Complete enrollment only" +msgid "Complete {{enrollmentLabel}} only" +msgstr "Complete {{enrollmentLabel}} only" -msgid "Would you like to complete the enrollment?" -msgstr "Would you like to complete the enrollment?" +msgid "Would you like to complete the {{enrollmentLabel}}?" +msgstr "Would you like to complete the {{enrollmentLabel}}?" -msgid "Complete enrollment" -msgstr "Complete enrollment" +msgid "Complete {{enrollmentLabel}}" +msgstr "Complete {{enrollmentLabel}}" msgid "A duplicate exists (but there were some errors, see log for details" msgstr "A duplicate exists (but there were some errors, see log for details" @@ -424,11 +422,11 @@ msgstr "Some operations are still running. Please wait." msgid "Operations running" msgstr "Operations running" -msgid "No feedback for this enrollment yet" -msgstr "No feedback for this enrollment yet" +msgid "No feedback for this {{enrollmentLabel}} yet" +msgstr "No feedback for this {{enrollmentLabel}} yet" -msgid "No indicator output for this enrollment yet" -msgstr "No indicator output for this enrollment yet" +msgid "No indicator output for this {{enrollmentLabel}} yet" +msgstr "No indicator output for this {{enrollmentLabel}} yet" msgid "" "This event has unsaved changes. Leaving this page without saving will lose " @@ -730,27 +728,27 @@ msgstr "Make referral" msgid "No available program stages" msgstr "No available program stages" -msgid "Invalid enrollment id {{enrollmentId}}." -msgstr "Invalid enrollment id {{enrollmentId}}." +msgid "Invalid {{enrollmentLabel}} id {{enrollmentId}}." +msgstr "Invalid {{enrollmentLabel}} id {{enrollmentId}}." -msgid "Choose an enrollment to view the dashboard." -msgstr "Choose an enrollment to view the dashboard." +msgid "Choose an {{enrollmentLabel}} to view the dashboard." +msgstr "Choose an {{enrollmentLabel}} to view the dashboard." msgid "" -"Choose a program to add new or see existing enrollments for " +"Choose a program to add new or see existing {{enrollmentsLabel}} for " "{{teiDisplayName}}" msgstr "" -"Choose a program to add new or see existing enrollments for " +"Choose a program to add new or see existing {{enrollmentsLabel}} for " "{{teiDisplayName}}" msgid "{{programName}} has categories. Choose all categories to view dashboard." msgstr "{{programName}} has categories. Choose all categories to view dashboard." -msgid "There are no active enrollments." -msgstr "There are no active enrollments." +msgid "There are no active {{enrollmentsLabel}}." +msgstr "There are no active {{enrollmentsLabel}}." -msgid "Add new enrollment for {{teiDisplayName}} in this program." -msgstr "Add new enrollment for {{teiDisplayName}} in this program." +msgid "Add new {{enrollmentLabel}} for {{teiDisplayName}} in this program." +msgstr "Add new {{enrollmentLabel}} for {{teiDisplayName}} in this program." msgid "" "You do not have permissions to access to this program, registering unit or " @@ -767,17 +765,16 @@ msgstr "Enroll {{teiDisplayName}} in this program." msgid "" "{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the " -"{{programName}}. Choose another program that allows {{tetName}} enrollment. " +"{{programName}}. Choose another program that allows {{tetName}} " +"{{enrollmentLabel}}. " msgstr "" "{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the " -"{{programName}}. Choose another program that allows {{tetName}} enrollment. " +"{{programName}}. Choose another program that allows {{tetName}} " +"{{enrollmentLabel}}. " msgid "Enroll a new {{selectedTetName}} in this program." msgstr "Enroll a new {{selectedTetName}} in this program." -msgid "{{programName}} is an event program and does not have enrollments." -msgstr "{{programName}} is an event program and does not have enrollments." - msgid "Create a new event in this program." msgstr "Create a new event in this program." @@ -793,9 +790,6 @@ msgstr "Tracked entity instance with id \"{{teiId}}\" does not exist" msgid "Program with id \"{{programId}}\" does not exist" msgstr "Program with id \"{{programId}}\" does not exist" -msgid "An error occurred while fetching enrollments. Please enter a valid url." -msgstr "An error occurred while fetching enrollments. Please enter a valid url." - msgid "An error has occurred" msgstr "An error has occurred" @@ -903,8 +897,8 @@ msgstr "New" msgid "You can also choose a program from the top bar and create in that program" msgstr "You can also choose a program from the top bar and create in that program" -msgid "New Enrollment in program{{escape}} {{programName}}" -msgstr "New Enrollment in program{{escape}} {{programName}}" +msgid "New {{enrollmentLabel}} in program{{escape}} {{programName}}" +msgstr "New {{enrollmentLabel}} in program{{escape}} {{programName}}" msgid "Save {{trackedEntityTypeName}}" msgstr "Save {{trackedEntityTypeName}}" @@ -1050,8 +1044,8 @@ msgstr "Search form is missing. See log for details" msgid "Could not retrieve metadata. Please try again later." msgstr "Could not retrieve metadata. Please try again later." -msgid "The enrollment event data could not be found" -msgstr "The enrollment event data could not be found" +msgid "The {{enrollmentLabel}} event data could not be found" +msgstr "The {{enrollmentLabel}} event data could not be found" msgid "Loading" msgstr "Loading" @@ -1074,8 +1068,8 @@ msgstr "Possible duplicates found" msgid "An error occurred loading possible duplicates" msgstr "An error occurred loading possible duplicates" -msgid "You only have view access to this enrollment" -msgstr "You only have view access to this enrollment" +msgid "You only have view access to this {{enrollmentLabel}}" +msgstr "You only have view access to this {{enrollmentLabel}}" msgid "You only have view access to this program" msgstr "You only have view access to this program" @@ -1306,31 +1300,31 @@ msgstr "No one is assigned to this event" msgid "Assign" msgstr "Assign" -msgid "Check for enrollments" -msgstr "Check for enrollments" +msgid "Check for {{enrollmentsLabel}}" +msgstr "Check for {{enrollmentsLabel}}" msgid "This program is protected" msgstr "This program is protected" msgid "" -"You must provide a reason to check for enrollments in this protected " -"program." +"You must provide a reason to check for {{enrollmentsLabel}} in this " +"protected program." msgstr "" -"You must provide a reason to check for enrollments in this protected " -"program." +"You must provide a reason to check for {{enrollmentsLabel}} in this " +"protected program." msgid "All activity will be logged." msgstr "All activity will be logged." -msgid "Reason to check for enrollments" -msgstr "Reason to check for enrollments" +msgid "Reason to check for {{enrollmentsLabel}}" +msgstr "Reason to check for {{enrollmentsLabel}}" msgid "" -"Describe the reason you are checking for enrollments in this protected " -"program" +"Describe the reason you are checking for {{enrollmentsLabel}} in this " +"protected program" msgstr "" -"Describe the reason you are checking for enrollments in this protected " -"program" +"Describe the reason you are checking for {{enrollmentsLabel}} in this " +"protected program" msgid "Unsaved changes" msgstr "Unsaved changes" @@ -1338,15 +1332,12 @@ msgstr "Unsaved changes" msgid "Continue data entry" msgstr "Continue data entry" -msgid "Enrollment actions" -msgstr "Enrollment actions" +msgid "{{enrollmentLabel}} actions" +msgstr "{{enrollmentLabel}} actions" msgid "We are processing your request." msgstr "We are processing your request." -msgid "Only one enrollment per {{tetName}} is allowed in this program" -msgstr "Only one enrollment per {{tetName}} is allowed in this program" - msgid "Add new" msgstr "Add new" @@ -1359,20 +1350,20 @@ msgstr "Mark as cancelled" msgid "Mark incomplete" msgstr "Mark incomplete" -msgid "You do not have access to delete this enrollment" -msgstr "You do not have access to delete this enrollment" +msgid "You do not have access to delete this {{enrollmentLabel}}" +msgstr "You do not have access to delete this {{enrollmentLabel}}" -msgid "Delete enrollment" -msgstr "Delete enrollment" +msgid "Delete {{enrollmentLabel}}" +msgstr "Delete {{enrollmentLabel}}" -msgid "Are you sure you want to delete this enrollment?" -msgstr "Are you sure you want to delete this enrollment?" +msgid "Are you sure you want to delete this {{enrollmentLabel}}?" +msgstr "Are you sure you want to delete this {{enrollmentLabel}}?" -msgid "This will permanently remove the current enrollment." -msgstr "This will permanently remove the current enrollment." +msgid "This will permanently remove the current {{enrollmentLabel}}." +msgstr "This will permanently remove the current {{enrollmentLabel}}." -msgid "Yes, delete enrollment." -msgstr "Yes, delete enrollment." +msgid "Yes, delete {{enrollmentLabel}}." +msgstr "Yes, delete {{enrollmentLabel}}." msgid "Remove mark for follow-up" msgstr "Remove mark for follow-up" @@ -1434,14 +1425,14 @@ msgid "Transfer Ownership" msgstr "Transfer Ownership" msgid "" -"Choose the organisation unit to which enrollment ownership should be " -"transferred." +"Choose the organisation unit to which {{enrollmentLabel}} ownership should " +"be transferred." msgstr "" -"Choose the organisation unit to which enrollment ownership should be " -"transferred." +"Choose the organisation unit to which {{enrollmentLabel}} ownership should " +"be transferred." -msgid "Enrollment date" -msgstr "Enrollment date" +msgid "{{enrollmentLabel}} date" +msgstr "{{enrollmentLabel}} date" msgid "Incident date" msgstr "Incident date" @@ -1476,14 +1467,14 @@ msgstr "Saving to {{stageName}} for {{programName}}" msgid "Program or program stage is invalid" msgstr "Program or program stage is invalid" -msgid "Notes about this enrollment" -msgstr "Notes about this enrollment" +msgid "Notes about this {{enrollmentLabel}}" +msgstr "Notes about this {{enrollmentLabel}}" -msgid "Write a note about this enrollment" -msgstr "Write a note about this enrollment" +msgid "Write a note about this {{enrollmentLabel}}" +msgstr "Write a note about this {{enrollmentLabel}}" -msgid "This enrollment doesn't have any notes" -msgstr "This enrollment doesn't have any notes" +msgid "This {{enrollmentLabel}} doesn't have any notes" +msgstr "This {{enrollmentLabel}} doesn't have any notes" msgid "Error" msgstr "Error" @@ -1605,8 +1596,12 @@ msgstr "Save changes" msgid "Change information about this {{trackedEntityName}} here." msgstr "Change information about this {{trackedEntityName}} here." -msgid "Information about this enrollment can be edited in the Enrollment widget." -msgstr "Information about this enrollment can be edited in the Enrollment widget." +msgid "" +"Information about this {{enrollmentLabel}} can be edited in the " +"{{enrollmentLabel}} widget." +msgstr "" +"Information about this {{enrollmentLabel}} can be edited in the " +"{{enrollmentLabel}} widget." msgid "{{trackedEntityName}} profile" msgstr "{{trackedEntityName}} profile" @@ -1667,15 +1662,6 @@ msgstr "You do not have access to delete this {{trackedEntityTypeName}}" msgid "Delete {{trackedEntityTypeName}}" msgstr "Delete {{trackedEntityTypeName}}" -msgid "" -"Are you sure you want to delete this {{trackedEntityTypeName}}? This will " -"permanently remove the {{trackedEntityTypeName}} and all its associated " -"enrollments and events in all programs." -msgstr "" -"Are you sure you want to delete this {{trackedEntityTypeName}}? This will " -"permanently remove the {{trackedEntityTypeName}} and all its associated " -"enrollments and events in all programs." - msgid "There was a problem deleting the {{trackedEntityTypeName}}" msgstr "There was a problem deleting the {{trackedEntityTypeName}}" @@ -1938,8 +1924,8 @@ msgstr "An error occurred while deleting the relationship." msgid "To open this relationship, please wait until saving is complete" msgstr "To open this relationship, please wait until saving is complete" -msgid "This enrollment doesn't have any relationships" -msgstr "This enrollment doesn't have any relationships" +msgid "This {{enrollmentLabel}} doesn't have any relationships" +msgstr "This {{enrollmentLabel}} doesn't have any relationships" msgid "Type" msgstr "Type" @@ -2010,8 +1996,8 @@ msgstr "Owner organisation unit" msgid "Registration Date" msgstr "Registration Date" -msgid "Enrollment status" -msgstr "Enrollment status" +msgid "{{enrollmentLabel}} status" +msgstr "{{enrollmentLabel}} status" msgid "Follow up" msgstr "Follow up" @@ -2019,121 +2005,135 @@ msgstr "Follow up" msgid "Choose a program stage to filter by {{label}}" msgstr "Choose a program stage to filter by {{label}}" -msgid "You do not have access to bulk complete enrollments" -msgstr "You do not have access to bulk complete enrollments" +msgid "You do not have access to bulk complete {{enrollmentsLabel}}" +msgstr "You do not have access to bulk complete {{enrollmentsLabel}}" msgid "" -"Some enrollments were completed successfully, but there was an error while " -"completing the rest. Please see the details below." +"Some {{enrollmentsLabel}} were completed successfully, but there was an " +"error while completing the rest. Please see the details below." msgstr "" -"Some enrollments were completed successfully, but there was an error while " -"completing the rest. Please see the details below." +"Some {{enrollmentsLabel}} were completed successfully, but there was an " +"error while completing the rest. Please see the details below." msgid "" -"There was an error while completing the enrollments. Please see the details " -"below." +"An unexpected error occurred while fetching the {{enrollmentsLabel}}. " +"Please try again." msgstr "" -"There was an error while completing the enrollments. Please see the details " -"below." - -msgid "" -"An unexpected error occurred while fetching the enrollments. Please try " -"again." -msgstr "" -"An unexpected error occurred while fetching the enrollments. Please try " -"again." - -msgid "There are currently no active enrollments in the selection." -msgstr "There are currently no active enrollments in the selection." +"An unexpected error occurred while fetching the {{enrollmentsLabel}}. " +"Please try again." -msgid "All enrollments are already completed or cancelled." -msgstr "All enrollments are already completed or cancelled." +msgid "There are currently no active {{enrollmentsLabel}} in the selection." +msgstr "There are currently no active {{enrollmentsLabel}} in the selection." -msgid "This action will complete {{count}} active enrollment in your selection." -msgid_plural "This action will complete {{count}} active enrollment in your selection." -msgstr[0] "This action will complete {{count}} active enrollment in your selection." -msgstr[1] "This action will complete {{count}} active enrollments in your selection." +msgid "All {{enrollmentsLabel}} are already completed or cancelled." +msgstr "All {{enrollmentsLabel}} are already completed or cancelled." -msgid "{{count}} enrollment already marked as completed will not be changed." -msgid_plural "{{count}} enrollment already marked as completed will not be changed." -msgstr[0] "{{count}} enrollment already marked as completed will not be changed." -msgstr[1] "{{count}} enrollments already marked as completed will not be changed." +msgid "" +"This action will complete {{count}} active {{enrollmentLabel}} in your " +"selection." +msgid_plural "" +"This action will complete {{count}} active {{enrollmentLabel}} in your " +"selection." +msgstr[0] "" +"This action will complete {{count}} active {{enrollmentLabel}} in your " +"selection." +msgstr[1] "" +"This action will complete {{count}} active {{enrollmentsLabel}} in your " +"selection." -msgid "Mark all events within enrollments as complete" -msgstr "Mark all events within enrollments as complete" +msgid "" +"{{count}} {{enrollmentLabel}} already marked as completed will not be " +"changed." +msgid_plural "" +"{{count}} {{enrollmentLabel}} already marked as completed will not be " +"changed." +msgstr[0] "" +"{{count}} {{enrollmentLabel}} already marked as completed will not be " +"changed." +msgstr[1] "" +"{{count}} {{enrollmentsLabel}} already marked as completed will not be " +"changed." -msgid "Complete enrollments" -msgstr "Complete enrollments" +msgid "Mark all events within {{enrollmentsLabel}} as complete" +msgstr "Mark all events within {{enrollmentsLabel}} as complete" -msgid "Error completing enrollments" -msgstr "Error completing enrollments" +msgid "Complete {{enrollmentsLabel}}" +msgstr "Complete {{enrollmentsLabel}}" -msgid "No active enrollments to complete" -msgstr "No active enrollments to complete" +msgid "Error completing {{enrollmentsLabel}}" +msgstr "Error completing {{enrollmentsLabel}}" -msgid "Complete {{count}} enrollment" -msgid_plural "Complete {{count}} enrollment" -msgstr[0] "Complete {{count}} enrollment" -msgstr[1] "Complete {{count}} enrollments" +msgid "No active {{enrollmentsLabel}} to complete" +msgstr "No active {{enrollmentsLabel}} to complete" -msgid "An error occurred when completing the enrollments" -msgstr "An error occurred when completing the enrollments" +msgid "Complete {{count}} {{enrollmentLabel}}" +msgid_plural "Complete {{count}} {{enrollmentLabel}}" +msgstr[0] "Complete {{count}} {{enrollmentLabel}}" +msgstr[1] "Complete {{count}} {{enrollmentsLabel}}" -msgid "An unknown error occurred when completing enrollments" -msgstr "An unknown error occurred when completing enrollments" +msgid "An error occurred when completing the {{enrollmentsLabel}}" +msgstr "An error occurred when completing the {{enrollmentsLabel}}" -msgid "You do not have access to delete enrollments" -msgstr "You do not have access to delete enrollments" +msgid "An unknown error occurred when completing {{enrollmentsLabel}}" +msgstr "An unknown error occurred when completing {{enrollmentsLabel}}" -msgid "Delete enrollments" -msgstr "Delete enrollments" +msgid "You do not have access to delete {{enrollmentsLabel}}" +msgstr "You do not have access to delete {{enrollmentsLabel}}" -msgid "Delete selected enrollments" -msgstr "Delete selected enrollments" +msgid "Delete {{enrollmentsLabel}}" +msgstr "Delete {{enrollmentsLabel}}" -msgid "An error occurred while loading the selected enrollments. Please try again." -msgstr "An error occurred while loading the selected enrollments. Please try again." +msgid "Delete selected {{enrollmentsLabel}}" +msgstr "Delete selected {{enrollmentsLabel}}" msgid "" -"This action will permanently delete the selected enrollments, including all " -"associated data and events." +"An error occurred while loading the selected {{enrollmentsLabel}}. Please " +"try again." msgstr "" -"This action will permanently delete the selected enrollments, including all " -"associated data and events." +"An error occurred while loading the selected {{enrollmentsLabel}}. Please " +"try again." -msgid "Active enrollments ({{count}})" -msgid_plural "Active enrollments ({{count}})" -msgstr[0] "Active enrollments ({{count}})" -msgstr[1] "Active enrollments ({{count}})" +msgid "" +"This action will permanently delete the selected {{enrollmentsLabel}}, " +"including all associated data and events." +msgstr "" +"This action will permanently delete the selected {{enrollmentsLabel}}, " +"including all associated data and events." -msgid "Completed enrollments ({{count}})" -msgid_plural "Completed enrollments ({{count}})" -msgstr[0] "Completed enrollments ({{count}})" -msgstr[1] "Completed enrollments ({{count}})" +msgid "Active {{enrollmentsLabel}} ({{count}})" +msgid_plural "Active {{enrollmentsLabel}} ({{count}})" +msgstr[0] "Active {{enrollmentsLabel}} ({{count}})" +msgstr[1] "Active {{enrollmentsLabel}} ({{count}})" -msgid "Cancelled enrollments ({{count}})" -msgid_plural "Cancelled enrollments ({{count}})" -msgstr[0] "Cancelled enrollments ({{count}})" -msgstr[1] "Cancelled enrollments ({{count}})" +msgid "Completed {{enrollmentsLabel}} ({{count}})" +msgid_plural "Completed {{enrollmentsLabel}} ({{count}})" +msgstr[0] "Completed {{enrollmentsLabel}} ({{count}})" +msgstr[1] "Completed {{enrollmentsLabel}} ({{count}})" -msgid "Delete {{count}} enrollment" -msgid_plural "Delete {{count}} enrollment" -msgstr[0] "Delete {{count}} enrollment" -msgstr[1] "Delete {{count}} enrollments" +msgid "Cancelled {{enrollmentsLabel}} ({{count}})" +msgid_plural "Cancelled {{enrollmentsLabel}} ({{count}})" +msgstr[0] "Cancelled {{enrollmentsLabel}} ({{count}})" +msgstr[1] "Cancelled {{enrollmentsLabel}} ({{count}})" -msgid "An error occurred when deleting enrollments" -msgstr "An error occurred when deleting enrollments" +msgid "Delete {{count}} {{enrollmentLabel}}" +msgid_plural "Delete {{count}} {{enrollmentLabel}}" +msgstr[0] "Delete {{count}} {{enrollmentLabel}}" +msgstr[1] "Delete {{count}} {{enrollmentsLabel}}" -msgid "Delete {{ trackedEntityName }} with all enrollments" -msgstr "Delete {{ trackedEntityName }} with all enrollments" +msgid "An error occurred when deleting {{enrollmentsLabel}}" +msgstr "An error occurred when deleting {{enrollmentsLabel}}" msgid "Delete {{count}} {{ trackedEntityName }}" msgid_plural "Delete {{count}} {{ trackedEntityName }}" msgstr[0] "Delete {{count}} {{ trackedEntityName }}" msgstr[1] "Delete {{count}} {{ trackedEntityName }}" -msgid "Deleting records will also delete any associated enrollments and events." -msgstr "Deleting records will also delete any associated enrollments and events." +msgid "" +"Deleting records will also delete any associated {{enrollmentsLabel}} and " +"events." +msgstr "" +"Deleting records will also delete any associated {{enrollmentsLabel}} and " +"events." msgid "Are you sure you want to delete?" msgstr "Are you sure you want to delete?" @@ -2239,9 +2239,18 @@ msgstr "Visited" msgid "{{trackedEntityName}} in program{{escape}} {{programName}}" msgstr "{{trackedEntityName}} in program{{escape}} {{programName}}" +msgid "enrollment" +msgstr "enrollment" + +msgid "enrollments" +msgstr "enrollments" + msgid "program stage" msgstr "program stage" +msgid "program stages" +msgstr "program stages" + msgid "note" msgstr "note" @@ -2284,24 +2293,12 @@ msgstr "Organisation unit search failed." msgid "Error saving tracked entity instance" msgstr "Error saving tracked entity instance" -msgid "Error saving enrollment" -msgstr "Error saving enrollment" - -msgid "Error saving the enrollment event" -msgstr "Error saving the enrollment event" - -msgid "Error deleting the enrollment event" -msgstr "Error deleting the enrollment event" - msgid "Error editing the event, the changes made were not saved" msgstr "Error editing the event, the changes made were not saved" msgid "Error updating the Assignee" msgstr "Error updating the Assignee" -msgid "Could not save enrollment note" -msgstr "Could not save enrollment note" - msgid "Could not save event note" msgstr "Could not save event note" diff --git a/src/core_modules/capture-core/components/Breadcrumbs/BulkDataEntryBreadcrumb/hooks/useOriginLabel.ts b/src/core_modules/capture-core/components/Breadcrumbs/BulkDataEntryBreadcrumb/hooks/useOriginLabel.ts index 7ec5c095b7..f3a088d899 100644 --- a/src/core_modules/capture-core/components/Breadcrumbs/BulkDataEntryBreadcrumb/hooks/useOriginLabel.ts +++ b/src/core_modules/capture-core/components/Breadcrumbs/BulkDataEntryBreadcrumb/hooks/useOriginLabel.ts @@ -2,6 +2,7 @@ import i18n from '@dhis2/d2-i18n'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { breadcrumbsKeys } from '../BulkDataEntryBreadcrumb'; +import { useTermLabel } from '../../../../metaData'; type Props = { programId: string; @@ -10,30 +11,35 @@ type Props = { page: string; }; -const DefaultFilterLabels = { - default: i18n.t('Program overview'), - active: i18n.t('Active enrollments'), - complete: i18n.t('Completed enrollments'), - cancelled: i18n.t('Cancelled enrollments'), -}; - -const getWorkingListLabel = (selectedTemplate: any, selectedTemplateId: string) => { +const getWorkingListLabel = ( + selectedTemplate: any, + selectedTemplateId: string, + defaultFilterLabels: Record, +) => { if (selectedTemplate && !selectedTemplate.isDefault) { return selectedTemplate.name; } if (selectedTemplateId && !selectedTemplate) { - return DefaultFilterLabels[selectedTemplateId as keyof typeof DefaultFilterLabels]; + return defaultFilterLabels[selectedTemplateId as keyof typeof defaultFilterLabels]; } return i18n.t('Program overview'); }; export const useOriginLabel = ({ programId, displayFrontPageList, page }: Props) => { + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); const workingListTemplates = useSelector(({ workingListsTemplates }: any) => workingListsTemplates?.teiList); const workingListProgramId = useSelector(({ workingListsContext }: any) => workingListsContext?.teiList?.programIdView); const { selectedTemplateId, loading: isLoadingTemplates, templates } = workingListTemplates ?? {}; const selectedTemplate = templates?.find(({ id }: any) => id === selectedTemplateId); const isSameProgram = workingListProgramId === programId; + const defaultFilterLabels = useMemo(() => ({ + default: i18n.t('Program overview'), + active: i18n.t('Active {{enrollmentsLabel}}', { enrollmentsLabel }), + complete: i18n.t('Completed {{enrollmentsLabel}}', { enrollmentsLabel }), + cancelled: i18n.t('Cancelled {{enrollmentsLabel}}', { enrollmentsLabel }), + }), [enrollmentsLabel]); + const label = useMemo(() => { if (page === breadcrumbsKeys.SEARCH_PAGE) { return i18n.t('Search'); @@ -44,7 +50,7 @@ export const useOriginLabel = ({ programId, displayFrontPageList, page }: Props) } if (isSameProgram) { - return getWorkingListLabel(selectedTemplate, selectedTemplateId); + return getWorkingListLabel(selectedTemplate, selectedTemplateId, defaultFilterLabels); } if (!displayFrontPageList) { @@ -58,6 +64,7 @@ export const useOriginLabel = ({ programId, displayFrontPageList, page }: Props) selectedTemplate, selectedTemplateId, page, + defaultFilterLabels, ]); return { diff --git a/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/EnrollmentBreadcrumb.tsx b/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/EnrollmentBreadcrumb.tsx index 2fc98e432e..2b4231e13c 100644 --- a/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/EnrollmentBreadcrumb.tsx +++ b/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/EnrollmentBreadcrumb.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState, ComponentType } from 'react'; import i18n from '@dhis2/d2-i18n'; import { withStyles, WithStyles } from 'capture-core-utils/styles'; import { colors } from '@dhis2/ui'; +import { useTermLabel } from '../../../metaData'; import { DirectionalChevron } from '../../../utils/rtl'; import { useWorkingListLabel } from './hooks/useWorkingListLabel'; import { BreadcrumbItem } from '../common/BreadcrumbItem'; @@ -68,6 +69,7 @@ const BreadcrumbsPlain = ({ classes, }: Props) => { const [openWarning, setOpenWarning] = useState(null); + const enrollmentLabel = useTermLabel('enrollment', { programId }); const { label } = useWorkingListLabel({ programId, @@ -101,7 +103,7 @@ const BreadcrumbsPlain = ({ { key: pageKeys.OVERVIEW, onClick: () => handleNavigation(onBackToDashboard, pageKeys.OVERVIEW), - label: i18n.t('Enrollment dashboard'), + label: i18n.t('{{enrollmentLabel}} dashboard', { enrollmentLabel }), selected: page === pageKeys.OVERVIEW, condition: true, }, @@ -135,6 +137,7 @@ const BreadcrumbsPlain = ({ onBackToMainPage, onBackToDashboard, onBackToViewEvent, + enrollmentLabel, ]); return ( diff --git a/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/hooks/useWorkingListLabel.ts b/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/hooks/useWorkingListLabel.ts index 130fa0efba..ab75045eb5 100644 --- a/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/hooks/useWorkingListLabel.ts +++ b/src/core_modules/capture-core/components/Breadcrumbs/EnrollmentBreadcrumb/hooks/useWorkingListLabel.ts @@ -1,6 +1,7 @@ import i18n from '@dhis2/d2-i18n'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; +import { useTermLabel } from '../../../../metaData'; type Template = { id: string; @@ -22,17 +23,11 @@ const DefaultFilterKeys = { type DefaultFilterKey = typeof DefaultFilterKeys[keyof typeof DefaultFilterKeys]; -const DefaultFilterLabels: { [key in DefaultFilterKey]: string } = { - [DefaultFilterKeys.DEFAULT]: i18n.t('Program overview'), - [DefaultFilterKeys.ACTIVE]: i18n.t('Active enrollments'), - [DefaultFilterKeys.COMPLETE]: i18n.t('Completed enrollments'), - [DefaultFilterKeys.CANCELLED]: i18n.t('Cancelled enrollments'), -}; - export const useWorkingListLabel = ({ programId, displayFrontPageList, }: Props) => { + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); const workingListTemplates = useSelector((state: any) => state.workingListsTemplates?.teiList); const workingListProgramId = useSelector((state: any) => state.workingListsContext?.teiList?.programIdView); @@ -41,6 +36,13 @@ export const useWorkingListLabel = ({ const selectedTemplate: Template | undefined = templates?.find(({ id }) => id === selectedTemplateId); const isSameProgram: boolean = workingListProgramId === programId; + const defaultFilterLabels: { [key in DefaultFilterKey]: string } = useMemo(() => ({ + [DefaultFilterKeys.DEFAULT]: i18n.t('Program overview'), + [DefaultFilterKeys.ACTIVE]: i18n.t('Active {{enrollmentsLabel}}', { enrollmentsLabel }), + [DefaultFilterKeys.COMPLETE]: i18n.t('Completed {{enrollmentsLabel}}', { enrollmentsLabel }), + [DefaultFilterKeys.CANCELLED]: i18n.t('Cancelled {{enrollmentsLabel}}', { enrollmentsLabel }), + }), [enrollmentsLabel]); + const label: string = useMemo(() => { if (isLoadingTemplates) return i18n.t('Loading...'); @@ -51,7 +53,7 @@ export const useWorkingListLabel = ({ if (selectedTemplateId && !selectedTemplate && DefaultFilterKeys[selectedTemplateId.toUpperCase() as keyof typeof DefaultFilterKeys]) { - return DefaultFilterLabels[selectedTemplateId as DefaultFilterKey]; + return defaultFilterLabels[selectedTemplateId as DefaultFilterKey]; } return i18n.t('Program overview'); @@ -66,6 +68,7 @@ export const useWorkingListLabel = ({ isSameProgram, selectedTemplate, selectedTemplateId, + defaultFilterLabels, ]); return { diff --git a/src/core_modules/capture-core/components/CardList/CardListButtons.component.tsx b/src/core_modules/capture-core/components/CardList/CardListButtons.component.tsx index 24500322a4..59ec17be9c 100644 --- a/src/core_modules/capture-core/components/CardList/CardListButtons.component.tsx +++ b/src/core_modules/capture-core/components/CardList/CardListButtons.component.tsx @@ -8,6 +8,7 @@ import { availableCardListButtonState, enrollmentTypes } from './CardList.consta import { navigateToEnrollmentOverview, } from '../../actions/navigateToEnrollmentOverview/navigateToEnrollmentOverview.actions'; +import { useTermLabel } from '../../metaData'; type Props = { currentSearchScopeId?: string, @@ -79,6 +80,7 @@ const CardListButtons: FC = ({ }) => { const dispatch = useDispatch(); const navigationButtonsState: AvailableCardListButtonState = deriveNavigationButtonState(enrollmentType); + const enrollmentLabel = useTermLabel('enrollment'); const onHandleClick: ButtonEventHandler> = useCallback((_, event) => { event.stopPropagation(); @@ -115,7 +117,7 @@ const CardListButtons: FC = ({ { dataTest: 'view-active-enrollment-button', onClick: onHandleClick, - label: i18n.t('View active enrollment'), + label: i18n.t('View active {{enrollmentLabel}}', { enrollmentLabel }), hide: navigationButtonsState !== availableCardListButtonState.SHOW_VIEW_ACTIVE_ENROLLMENT_BUTTON, }, { diff --git a/src/core_modules/capture-core/components/CardList/CardListItem.component.tsx b/src/core_modules/capture-core/components/CardList/CardListItem.component.tsx index 54d3ea1ec7..5177c463f0 100644 --- a/src/core_modules/capture-core/components/CardList/CardListItem.component.tsx +++ b/src/core_modules/capture-core/components/CardList/CardListItem.component.tsx @@ -17,6 +17,7 @@ import { getTrackerProgramThrowIfNotFound, OptionSet, type TrackerProgram, + useTermLabel, } from '../../metaData'; import { useOrgUnitNameWithAncestors } from '../../metadataRetrieval/orgUnitName'; import type { ListItem, RenderCustomCardActions } from './CardList.types'; @@ -152,6 +153,7 @@ const CardListItemIndex = ({ const enrollmentType = deriveEnrollmentType(enrollments, currentProgramId); const { orgUnitId, enrolledAt } = deriveEnrollmentOrgUnitIdAndDate(enrollments, enrollmentType, currentProgramId); const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId ?? null); + const enrollmentLabel = useTermLabel('enrollment', { programId: currentProgramId }); const program: TrackerProgram | undefined = enrollments.length ? deriveProgramFromEnrollment(enrollments, currentSearchScopeType) : undefined; @@ -221,7 +223,7 @@ const CardListItemIndex = ({ value={orgUnitName} /> diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx index 81802aa393..15dcfb016f 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx @@ -33,7 +33,7 @@ import { getIncidentDateValidatorContainer, } from './fieldValidators'; import { sectionKeysForEnrollmentDataEntry } from './constants/sectionKeys.const'; -import { type Enrollment, ProgramStage, RenderFoundation, getProgramThrowIfNotFound } from '../../../metaData'; +import { type Enrollment, ProgramStage, RenderFoundation, getProgramThrowIfNotFound, getTermLabel } from '../../../metaData'; import { EnrollmentWithFirstStageDataEntry } from './EnrollmentWithFirstStageDataEntry'; import { getCategoryOptionsValidatorContainers, @@ -362,30 +362,31 @@ class FinalEnrollmentDataEntry extends React.Component { inMemoryFileStore.clear(); } - static dataEntrySectionDefinitions = { - [sectionKeysForEnrollmentDataEntry.ENROLLMENT]: { - placement: placements.TOP, - name: i18n.t('Enrollment'), - }, - [AOCsectionKey]: { - placement: placements.BOTTOM, - }, - }; - render() { - const { enrollmentMetadata, firstStageMetaData, relatedStageActionsOptions, ...passOnProps } = this.props; + const { enrollmentMetadata, firstStageMetaData, relatedStageActionsOptions, programId, ...passOnProps } = this.props; + + const dataEntrySections = { + [sectionKeysForEnrollmentDataEntry.ENROLLMENT]: { + placement: placements.TOP, + name: getTermLabel(programId, 'enrollment'), + }, + [AOCsectionKey]: { + placement: placements.BOTTOM, + }, + }; return ( firstStageMetaData ? ( ) : ( ) ); diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/hooks/useDataEntrySections.ts b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/hooks/useDataEntrySections.ts index 3f2d62063a..32fd41353a 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/hooks/useDataEntrySections.ts +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/hooks/useDataEntrySections.ts @@ -3,13 +3,15 @@ import i18n from '@dhis2/d2-i18n'; import { placements } from '../../../../DataEntry/constants/placements.const'; import { sectionKeysForFirstStageDataEntry } from '../EnrollmentWithFirstStageDataEntry.constants'; import { AOCsectionKey } from '../../../../DataEntryDhis2Helpers'; +import { useTermLabel } from '../../../../../metaData'; -export const useDataEntrySections = (stageName: string, beforeSectionId: string) => - useMemo( +export const useDataEntrySections = (stageName: string, beforeSectionId: string) => { + const enrollmentLabel = useTermLabel('enrollment'); + return useMemo( () => ({ [sectionKeysForFirstStageDataEntry.ENROLLMENT]: { placement: placements.TOP, - name: i18n.t('Enrollment'), + name: enrollmentLabel, }, [sectionKeysForFirstStageDataEntry.STAGE_BASIC_INFO]: { beforeSectionId, @@ -35,5 +37,6 @@ export const useDataEntrySections = (stageName: string, beforeSectionId: string) }), }, }), - [stageName, beforeSectionId], + [stageName, beforeSectionId, enrollmentLabel], ); +}; diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.tsx b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.tsx index 19b8892892..2216ad2a97 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.tsx @@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { withStyles, WithStyles } from 'capture-core-utils/styles'; import { compose } from 'redux'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; -import { scopeTypes } from '../../../metaData'; +import { scopeTypes, useTermLabel } from '../../../metaData'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; import { EnrollmentDataEntry } from '../Enrollment'; import type { Props, PlainProps } from './EnrollmentRegistrationEntry.types'; @@ -26,10 +26,12 @@ const translatedTextWithStylesForProgram = ( trackedEntityName: string, programName: string, orgUnitName: string, + enrollmentLabel: string, teiId?: string, ) => ( teiId ? - {i18n.t('Saving a new enrollment in {{programName}} in {{orgUnitName}}.', { + {i18n.t('Saving a new {{enrollmentLabel}} in {{programName}} in {{orgUnitName}}.', { + enrollmentLabel, programName, orgUnitName, interpolation: { escapeValue: false }, @@ -65,6 +67,7 @@ const EnrollmentRegistrationEntryPlain = }: PlainProps & WithStyles) => { const [showWarning, setShowWarning] = useState(false); const { scopeType, trackedEntityName, programName } = useScopeInfo(selectedScopeId); + const enrollmentLabel = useTermLabel('enrollment'); const handleOnCancel = () => { if (!isUserInteractionInProgress) { @@ -122,6 +125,7 @@ const EnrollmentRegistrationEntryPlain = trackedEntityName.toLowerCase(), programName, orgUnit.name, + enrollmentLabel, teiId, )} diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.tsx b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.tsx index fff77883c9..722bd1b083 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.tsx @@ -4,7 +4,7 @@ import { Button, spacers } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import { withStyles, WithStyles } from 'capture-core-utils/styles'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; -import { scopeTypes } from '../../../metaData'; +import { scopeTypes, useTermLabel } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; import { useOrgUnitNameWithAncestors } from '../../../metadataRetrieval/orgUnitName'; @@ -19,13 +19,14 @@ import { useMetadataForRegistrationForm } from '../common/TEIAndEnrollment/useMe const translatedTextWithStylesForTei = ( trackedEntityName: string, + enrollmentLabel: string, orgUnitName?: string, hideProgramSelectionMessage?: boolean, ) => (<> {i18n.t('Saving a {{trackedEntityName}}', { trackedEntityName, interpolation: { escapeValue: false } }) - } {i18n.t('without')} {i18n.t('enrollment')} + } {i18n.t('without')} {enrollmentLabel} {orgUnitName && <>{' '}{i18n.t('in')} {orgUnitName}}.{' '} {!hideProgramSelectionMessage && i18n.t('Enroll in a program by selecting a program from the top bar.')} ); @@ -59,6 +60,7 @@ const TeiRegistrationEntryPlain = const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); const orgUnitId = useCurrentOrgUnitId(); const { displayName: orgUnitName } = useOrgUnitNameWithAncestors(orgUnitId); + const enrollmentLabel = useTermLabel('enrollment'); const handleOnCancel = () => { if (!isUserInteractionInProgress) { @@ -111,7 +113,7 @@ const TeiRegistrationEntryPlain = {translatedTextWithStylesForTei( - trackedEntityName.toLowerCase(), orgUnitName, hideProgramSelectionMessage, + trackedEntityName.toLowerCase(), enrollmentLabel, orgUnitName, hideProgramSelectionMessage, )} diff --git a/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/CompleteModal/CompleteModal.component.tsx b/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/CompleteModal/CompleteModal.component.tsx index 08e3dd866a..db8fe7a829 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/CompleteModal/CompleteModal.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/CompleteModal/CompleteModal.component.tsx @@ -2,6 +2,7 @@ import { Modal, ModalActions, ModalContent, ModalTitle, Button, ButtonStrip } fr import React from 'react'; import i18n from '@dhis2/d2-i18n'; import type { PlainProps, PlainPropsWithEvents } from './completeModal.types'; +import { useTermLabel } from '../../../../../../metaData'; export const CompleteEnrollmentAndEventsModalComponent = ({ programStageName, @@ -10,95 +11,104 @@ export const CompleteEnrollmentAndEventsModalComponent = ({ onCancel, onCompleteEnrollmentAndEvents, onCompleteEnrollment, -}: PlainPropsWithEvents) => ( - - - {i18n.t('{{programStageName}} completed', { - programStageName, - interpolation: { escapeValue: false }, - })} - - -

{i18n.t('Would you like to complete the enrollment and all active events as well?')}

+}: PlainPropsWithEvents) => { + const enrollmentLabel = useTermLabel('enrollment'); + return ( + + + {i18n.t('{{programStageName}} completed', { + programStageName, + interpolation: { escapeValue: false }, + })} + + +

{i18n.t( + 'Would you like to complete the {{enrollmentLabel}} and all active events as well?', + { enrollmentLabel }, + )}

- {Object.keys(programStagesWithActiveEvents).length !== 0 && ( - <> - {i18n.t('The following events will be completed:')} - {Object.keys(programStagesWithActiveEvents).map((key) => { - const { count, name } = programStagesWithActiveEvents[key]; - return ( -
    - {i18n.t('{{count}} event in {{programStageName}}', { - count, - defaultValue: '{{count}} event in {{programStageName}}', - defaultValue_plural: '{{count}} events in {{programStageName}}', - programStageName: name, - interpolation: { escapeValue: false }, - })} -
- ); - })} - - )} + {Object.keys(programStagesWithActiveEvents).length !== 0 && ( + <> + {i18n.t('The following events will be completed:')} + {Object.keys(programStagesWithActiveEvents).map((key) => { + const { count, name } = programStagesWithActiveEvents[key]; + return ( +
    + {i18n.t('{{count}} event in {{programStageName}}', { + count, + defaultValue: '{{count}} event in {{programStageName}}', + defaultValue_plural: '{{count}} events in {{programStageName}}', + programStageName: name, + interpolation: { escapeValue: false }, + })} +
+ ); + })} + + )} - {Object.keys(programStagesWithoutAccess).length !== 0 && ( - <> - {i18n.t('The following events will not be completed due to lack of access:')} - {Object.keys(programStagesWithoutAccess).map((key) => { - const { count, name } = programStagesWithoutAccess[key]; + {Object.keys(programStagesWithoutAccess).length !== 0 && ( + <> + {i18n.t('The following events will not be completed due to lack of access:')} + {Object.keys(programStagesWithoutAccess).map((key) => { + const { count, name } = programStagesWithoutAccess[key]; - return ( -
    - {i18n.t('{{count}} event in {{programStageName}}', { - count, - defaultValue: '{{count}} event in {{programStageName}}', - defaultValue_plural: '{{count}} events in {{programStageName}}', - programStageName: name, - interpolation: { escapeValue: false }, - })} -
- ); - })} - - )} + return ( +
    + {i18n.t('{{count}} event in {{programStageName}}', { + count, + defaultValue: '{{count}} event in {{programStageName}}', + defaultValue_plural: '{{count}} events in {{programStageName}}', + programStageName: name, + interpolation: { escapeValue: false }, + })} +
+ ); + })} + + )} - - - - - - - -
-
-); + + + + + + + +
+
+ ); +}; -export const CompleteEnrollmentModalComponent = ({ programStageName, onCancel, onCompleteEnrollment }: PlainProps) => ( - - - {i18n.t('{{programStageName}} completed', { - programStageName, - interpolation: { escapeValue: false }, - })} - - -

{i18n.t('Would you like to complete the enrollment?')}

- - - - - - -
-
-); +export const CompleteEnrollmentModalComponent = ({ programStageName, onCancel, onCompleteEnrollment }: PlainProps) => { + const enrollmentLabel = useTermLabel('enrollment'); + return ( + + + {i18n.t('{{programStageName}} completed', { + programStageName, + interpolation: { escapeValue: false }, + })} + + +

{i18n.t('Would you like to complete the {{enrollmentLabel}}?', { enrollmentLabel })}

+ + + + + + +
+
+ ); +}; diff --git a/src/core_modules/capture-core/components/DataEntryWidgetOutput/DataEntryWidgetOutput.container.ts b/src/core_modules/capture-core/components/DataEntryWidgetOutput/DataEntryWidgetOutput.container.ts index bca310bb95..bbb9a81196 100644 --- a/src/core_modules/capture-core/components/DataEntryWidgetOutput/DataEntryWidgetOutput.container.ts +++ b/src/core_modules/capture-core/components/DataEntryWidgetOutput/DataEntryWidgetOutput.container.ts @@ -4,6 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { DataEntryWidgetOutputComponent } from './DataEntryWidgetOutput.component'; import { getDataEntryKey } from '../DataEntry/common/getDataEntryKey'; import { makeProgramRulesSelector } from './DataEntryWidgetOutput.selectors'; +import { getTermLabel } from '../../metaData'; type OwnProps = { dataEntryId: string; @@ -17,13 +18,15 @@ const makeMapStateToProps = () => { const { dataEntries } = state; const ready = !!dataEntries[dataEntryId]; const dataEntryKey = ready ? getDataEntryKey(dataEntryId, state.dataEntries[dataEntryId].itemId) : null; + const programId = state.currentSelections?.programId; + const enrollmentLabel = getTermLabel(programId, 'enrollment'); return { ready, dataEntryKey, programRules: programRulesSelector(state, { dataEntryId, selectedScopeId }), - feedbackEmptyText: i18n.t('No feedback for this enrollment yet'), - indicatorEmptyText: i18n.t('No indicator output for this enrollment yet'), + feedbackEmptyText: i18n.t('No feedback for this {{enrollmentLabel}} yet', { enrollmentLabel }), + indicatorEmptyText: i18n.t('No indicator output for this {{enrollmentLabel}} yet', { enrollmentLabel }), }; }; }; diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx index bfa61fbf85..36b9c1de43 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentPageDefault.container.tsx @@ -6,6 +6,7 @@ import { formatMomentEn } from 'capture-core-utils/date'; import { useDispatch, useSelector } from 'react-redux'; import { useTimeZoneConversion } from '@dhis2/app-runtime'; import type { ApiEnrollmentEvent } from 'capture-core-utils/types/api-types'; +import { useTermLabel } from '../../../../metaData'; import { commitEnrollmentAndEvents, EnrollmentAccessProvider, @@ -53,6 +54,7 @@ export const EnrollmentPageDefault = () => { const { fromClientDate } = useTimeZoneConversion(); const { status: widgetEnrollmentStatus } = useSelector(({ widgetEnrollment }: any) => widgetEnrollment); const { enrollmentId, programId, teiId, orgUnitId } = useLocationQuery(); + const enrollmentLabel = useTermLabel('enrollment', { programId }); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); const { onLinkedRecordClick } = useLinkedRecordClick(); const { @@ -233,8 +235,8 @@ export const EnrollmentPageDefault = () => { ruleEffects={ruleEffects} widgetEnrollmentStatus={widgetEnrollmentStatus} onAccessLostFromTransfer={onAccessLostFromTransfer} - feedbackEmptyText={i18n.t('No feedback for this enrollment yet')} - indicatorEmptyText={i18n.t('No indicator output for this enrollment yet')} + feedbackEmptyText={i18n.t('No feedback for this {{enrollmentLabel}} yet', { enrollmentLabel })} + indicatorEmptyText={i18n.t('No indicator output for this {{enrollmentLabel}} yet', { enrollmentLabel })} /> ); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.tsx b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.tsx index fa1043c8e2..0a05eccfda 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.tsx +++ b/src/core_modules/capture-core/components/Pages/Enrollment/MissingMessage.component.tsx @@ -5,6 +5,8 @@ import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { useMissingCategoriesInProgramSelection } from '../../../hooks/useMissingCategoriesInProgramSelection'; import { scopeTypes } from '../../../metaData/helpers/constants'; +import { useTermLabel } from '../../../metaData'; +import { tCustomTerm } from '../../../utils/tCustomTerm'; import { enrollmentAccessLevels } from './EnrollmentPage.constants'; import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../utils/routing'; import { IncompleteSelectionsMessage } from '../../IncompleteSelectionsMessage'; @@ -151,17 +153,21 @@ const styles: Readonly = { }, }; -const EnrollmentSelectionMessage = ({ enrollmentId }: { enrollmentId?: string }) => ( - - {enrollmentId ? - i18n.t('Invalid enrollment id {{enrollmentId}}.', { - enrollmentId, - interpolation: { escapeValue: false }, - }) : - i18n.t('Choose an enrollment to view the dashboard.') - } - -); +const EnrollmentSelectionMessage = ({ enrollmentId }: { enrollmentId?: string }) => { + const enrollmentLabel = useTermLabel('enrollment'); + return ( + + {enrollmentId ? + i18n.t('Invalid {{enrollmentLabel}} id {{enrollmentId}}.', { + enrollmentLabel, + enrollmentId, + interpolation: { escapeValue: false }, + }) : + i18n.t('Choose an {{enrollmentLabel}} to view the dashboard.', { enrollmentLabel }) + } + + ); +}; type PlainProps = Record; @@ -180,6 +186,8 @@ const MissingMessagePlain = ({ const { resetTeiId } = useResetTeiId(); const { teiDisplayName, tetId } = useSelector(({ enrollmentPage }: any) => enrollmentPage); const { programId, teiId, enrollmentId } = useLocationQuery(); + const enrollmentLabel = useTermLabel('enrollment'); + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); const { trackedEntityName: tetName } = useScopeInfo(tetId); const { programName, trackedEntityName: selectedTetName } = useScopeInfo(programId); @@ -188,8 +196,8 @@ const MissingMessagePlain = ({ { missingStatus === missingStatuses.MISSING_PROGRAM_SELECTION && - {i18n.t('Choose a program to add new or see existing enrollments for {{teiDisplayName}}', { - teiDisplayName, interpolation: { escapeValue: false }, + {i18n.t('Choose a program to add new or see existing {{enrollmentsLabel}} for {{teiDisplayName}}', { + enrollmentsLabel, teiDisplayName, interpolation: { escapeValue: false }, })} } @@ -213,13 +221,16 @@ const MissingMessagePlain = ({ missingStatus === missingStatuses.MISSING_ENROLLMENT_SELECTION_ADD_NEW &&
- {i18n.t('There are no active enrollments.')} + {i18n.t('There are no active {{enrollmentsLabel}}.', { enrollmentsLabel })}
- {i18n.t('Add new enrollment for {{teiDisplayName}} in this program.', { teiDisplayName })} + {i18n.t( + 'Add new {{enrollmentLabel}} for {{teiDisplayName}} in this program.', + { enrollmentLabel, teiDisplayName }, + )}
@@ -271,8 +282,8 @@ const MissingMessagePlain = ({
{/* eslint-disable-next-line max-len */} - {i18n.t('{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the {{programName}}. Choose another program that allows {{tetName}} enrollment. ', { - teiDisplayName, programName, tetName, interpolation: { escapeValue: false }, + {i18n.t('{{teiDisplayName}} is a {{tetName}} and cannot be enrolled in the {{programName}}. Choose another program that allows {{tetName}} {{enrollmentLabel}}. ', { + teiDisplayName, programName, tetName, enrollmentLabel, interpolation: { escapeValue: false }, })}
- {i18n.t('{{programName}} is an event program and does not have enrollments.', { - programName, interpolation: { escapeValue: false }, + {tCustomTerm('{{programName}} is an event program and does not have {{enrollmentsLabel}}.', { + programName, enrollmentsLabel, })}
{ + const enrollmentLabel = useTermLabel('enrollment', { programId }); const { setProgramIdAndResetEnrollmentContext } = useSetProgramId(); const { setOrgUnitId } = useSetOrgUnitId(); const { setEnrollmentId } = useSetEnrollmentId(); @@ -73,7 +74,7 @@ export const TopBar = ({ onSelect={id => setEnrollmentId({ enrollmentId: id })} options={enrollmentsAsOptions} selectedValue={enrollmentId} - title={i18n.t('Enrollment')} + title={enrollmentLabel} /> ) : <>} diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/epics/fetchEnrollment.epics.ts b/src/core_modules/capture-core/components/Pages/Enrollment/epics/fetchEnrollment.epics.ts index 11229927ea..c786028e3f 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/epics/fetchEnrollment.epics.ts +++ b/src/core_modules/capture-core/components/Pages/Enrollment/epics/fetchEnrollment.epics.ts @@ -2,8 +2,9 @@ import { ofType } from 'redux-observable'; import { catchError, concatMap, map } from 'rxjs/operators'; import { from, of } from 'rxjs'; import moment from 'moment'; -import i18n from '@dhis2/d2-i18n'; import { FEATURES, featureAvailable } from 'capture-core-utils'; +import { getTermLabel } from '../../../../metaData'; +import { tCustomTerm } from '../../../../utils/tCustomTerm'; import { systemSettingsStore } from '../../../../metaDataMemoryStores'; import { @@ -119,12 +120,15 @@ const handleErrorsFromNewerBackends = ({ querySingleResource, })); } - const errorMessage = - i18n.t('An error occurred while fetching enrollments. Please enter a valid url.'); + const enrollmentsLabel = getTermLabel(programId, 'enrollment', { plural: true }); + const errorMessage = tCustomTerm( + 'An error occurred while fetching {{enrollmentsLabel}}. Please enter a valid url.', + { enrollmentsLabel }, + ); return of(showErrorViewOnEnrollmentPage({ error: errorMessage })); }; -const handleErrorsFromOlderBackends = (error: any) => { +const handleErrorsFromOlderBackends = (error: any, programId?: string) => { const { message } = error || {}; if (message) { if (message.includes(serverErrorMessages.OWNERSHIP_ACCESS_PARTIALLY_DENIED)) { @@ -137,7 +141,11 @@ const handleErrorsFromOlderBackends = (error: any) => { return fetchEnrollmentsError({ accessLevel: enrollmentAccessLevels.NO_ACCESS }); } } - const errorMessage = i18n.t('An error occurred while fetching enrollments. Please enter a valid url.'); + const enrollmentsLabel = getTermLabel(programId, 'enrollment', { plural: true }); + const errorMessage = tCustomTerm( + 'An error occurred while fetching {{enrollmentsLabel}}. Please enter a valid url.', + { enrollmentsLabel }, + ); return showErrorViewOnEnrollmentPage({ error: errorMessage }); }; @@ -165,7 +173,7 @@ export const fetchEnrollmentsEpic = (action$: any, store: any, { querySingleReso querySingleResource, }); } - return of(handleErrorsFromOlderBackends(error)); + return of(handleErrorsFromOlderBackends(error, programId)); }), map((action: any) => verifyFetchedEnrollments({ teiId, programId, action })), ); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/TopBar/TopBar.component.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/TopBar/TopBar.component.tsx index b79e06c752..e830669970 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/TopBar/TopBar.component.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/TopBar/TopBar.component.tsx @@ -1,5 +1,6 @@ import React from 'react'; import i18n from '@dhis2/d2-i18n'; +import { useTermLabel } from '../../../../metaData'; import { ScopeSelector, SingleLockedSelect, useReset } from '../../../ScopeSelector'; import { TopBarActions } from '../../../TopBarActions'; import type { Props } from './topBar.types'; @@ -24,6 +25,7 @@ export const EnrollmentAddEventTopBar = ({ enrollmentsAsOptions, }: Props) => { const { reset } = useReset(); + const enrollmentLabel = useTermLabel('enrollment', { programId }); return ( onResetEnrollmentId()} options={enrollmentsAsOptions || []} selectedValue={enrollmentId} - title={i18n.t('Enrollment')} + title={enrollmentLabel} isUserInteractionInProgress={userInteractionInProgress} /> {stageName && ( diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.tsx b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.tsx index dc2357d1a3..cf7c2d9d17 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.tsx +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/TopBar.container.tsx @@ -1,7 +1,7 @@ import React from 'react'; import i18n from '@dhis2/d2-i18n'; import { dataEntryKeys } from 'capture-core/constants'; -import type { ProgramStage } from '../../../metaData'; +import { useTermLabel, type ProgramStage } from '../../../metaData'; import { pageStatuses } from './EnrollmentEditEventPage.constants'; import { ScopeSelector, @@ -47,6 +47,7 @@ export const TopBar = ({ isUserInteractionInProgress, }: Props) => { const { setOrgUnitId } = useSetOrgUnitId(); + const enrollmentLabel = useTermLabel('enrollment', { programId: programId ?? undefined }); const { resetProgramIdAndEnrollmentContext } = useResetProgramId(); const { resetOrgUnitId } = useResetOrgUnitId(); @@ -87,7 +88,7 @@ export const TopBar = ({ onClear={() => resetEnrollmentId('enrollment', { programId: programId ?? undefined, teiId })} options={enrollmentsAsOptions} selectedValue={enrollmentId} - title={i18n.t('Enrollment')} + title={enrollmentLabel} isUserInteractionInProgress={isUserInteractionInProgress} /> { let url; @@ -198,7 +199,8 @@ const RegistrationDataEntryPlain = ({
{ - teiId ? i18n.t('New Enrollment in program{{escape}} {{programName}}', { + teiId ? i18n.t('New {{enrollmentLabel}} in program{{escape}} {{programName}}', { + enrollmentLabel, escape: ':', programName, interpolation: { escapeValue: false }, diff --git a/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.tsx b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.tsx index 986655fa08..0862f82a0c 100644 --- a/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.tsx +++ b/src/core_modules/capture-core/components/Pages/common/WidgetEventEditWrapper/WidgetEventEditWrapper.tsx @@ -5,6 +5,7 @@ import { IncompleteSelectionsMessage } from '../../../IncompleteSelectionsMessag import { WidgetEventEdit } from '../../../WidgetEventEdit'; import type { Props } from '../../../WidgetEventEdit/widgetEventEdit.types'; import { useMetadataForProgramStage } from '../../../DataEntries/common/ProgramStage/useMetadataForProgramStage'; +import { useTermLabel } from '../../../../metaData'; type WidgetProps = { pageStatus: string; @@ -22,6 +23,7 @@ export const WidgetEventEditWrapper = ({ pageStatus, ...passOnProps }: WidgetPro isLoading, isError, } = useMetadataForProgramStage({ programId, stageId }); + const enrollmentLabel = useTermLabel('enrollment', { programId }); if (pageStatus === pageStatuses.WITHOUT_ORG_UNIT_SELECTED) { return ( @@ -33,7 +35,7 @@ export const WidgetEventEditWrapper = ({ pageStatus, ...passOnProps }: WidgetPro if (pageStatus === pageStatuses.MISSING_DATA) { return ( - {i18n.t('The enrollment event data could not be found')} + {i18n.t('The {{enrollmentLabel}} event data could not be found', { enrollmentLabel })} ); } diff --git a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.tsx b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.tsx index ffffb97b53..04e50ae1c4 100644 --- a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.tsx +++ b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.tsx @@ -4,6 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import { ConditionalTooltip } from '../Tooltips/ConditionalTooltip'; import type { Props, Access, ReadOnlyMessageInput } from './ReadOnlyBadge.types'; +import { useTermLabel } from '../../metaData'; const styles = { label: { @@ -11,7 +12,8 @@ const styles = { }, } as const; -const getEnrollmentMessage = (): string => i18n.t('You only have view access to this enrollment'); +const getEnrollmentMessage = (enrollmentLabel: string): string => + i18n.t('You only have view access to this {{enrollmentLabel}}', { enrollmentLabel }); const getProgramMessage = (): string => i18n.t('You only have view access to this program'); @@ -40,9 +42,10 @@ const getReadOnlyMessage = ({ canEditCompletedEvent, withinCompleteEventsExpiry, trackedEntityInactive, + enrollmentLabel, }: ReadOnlyMessageInput): string => { if (trackedEntityInactive) return getDeactivatedMessage(trackedEntityName); - if (!access.program && !access.trackedEntityType && !access.programStage) return getEnrollmentMessage(); + if (!access.program && !access.trackedEntityType && !access.programStage) return getEnrollmentMessage(enrollmentLabel); if (!access.program) return getProgramMessage(); if (!access.trackedEntityType) return getTrackedEntityMessage(trackedEntityName); if (!access.programStage) return getProgramStageMessage(multipleStages); @@ -65,6 +68,7 @@ const ReadOnlyBadgePlain = ({ inlineLabel = false, classes, }: Props & WithStyles) => { + const enrollmentLabel = useTermLabel('enrollment'); const access: Access = { program: programWriteAccess, trackedEntityType: trackedEntityTypeWriteAccess, @@ -78,6 +82,7 @@ const ReadOnlyBadgePlain = ({ canEditCompletedEvent, withinCompleteEventsExpiry, trackedEntityInactive, + enrollmentLabel, }); if (!message) return null; diff --git a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts index a7ada9cb08..d1f94a4f4b 100644 --- a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts +++ b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts @@ -25,4 +25,5 @@ export type ReadOnlyMessageInput = { canEditCompletedEvent: boolean; withinCompleteEventsExpiry: boolean; trackedEntityInactive: boolean; + enrollmentLabel: string; }; diff --git a/src/core_modules/capture-core/components/Relationships/Relationships.component.tsx b/src/core_modules/capture-core/components/Relationships/Relationships.component.tsx index 7b8ccb42e9..69c2596a9f 100644 --- a/src/core_modules/capture-core/components/Relationships/Relationships.component.tsx +++ b/src/core_modules/capture-core/components/Relationships/Relationships.component.tsx @@ -63,10 +63,9 @@ const styles: Readonly = (theme: any) => ({ }, }); -const getFromName = (entityType: string) => { - if (entityType === 'PROGRAM_STAGE_INSTANCE') return i18n.t('This event'); - return undefined; -}; +const getFromNames = () => ({ + PROGRAM_STAGE_INSTANCE: i18n.t('This event'), +}); type PlainProps = { relationships: Array; @@ -106,7 +105,7 @@ class RelationshipsPlain extends React.Component { const { onRenderConnectedEntity } = this.props; if (entity.id === this.props.currentEntityId) { - return getFromName(entity.type); + return getFromNames()[entity.type]; } return onRenderConnectedEntity ? onRenderConnectedEntity(entity) : entity.name; diff --git a/src/core_modules/capture-core/components/WidgetBreakingTheGlass/WidgetBreakingTheGlass.component.tsx b/src/core_modules/capture-core/components/WidgetBreakingTheGlass/WidgetBreakingTheGlass.component.tsx index 494b3c5ba1..63da23437e 100644 --- a/src/core_modules/capture-core/components/WidgetBreakingTheGlass/WidgetBreakingTheGlass.component.tsx +++ b/src/core_modules/capture-core/components/WidgetBreakingTheGlass/WidgetBreakingTheGlass.component.tsx @@ -10,6 +10,7 @@ import i18n from '@dhis2/d2-i18n'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import type { PlainProps } from './WidgetBreakingTheGlass.types'; import { Widget } from '../Widget'; +import { useTermLabel } from '../../metaData'; const styles: Readonly = ({ typography }: any) => ({ title: { @@ -35,6 +36,7 @@ const WidgetBreakingTheGlassPlain = ({ setReason(value); }, [setReason]); const disabled = useMemo(() => reason.length === 0, [reason]); + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); return (
@@ -45,19 +47,25 @@ const WidgetBreakingTheGlassPlain = ({ >
- {i18n.t('Check for enrollments')} + {i18n.t('Check for {{enrollmentsLabel}}', { enrollmentsLabel })}

- {i18n.t('You must provide a reason to check for enrollments in this protected program.')} + {i18n.t( + 'You must provide a reason to check for {{enrollmentsLabel}} in this protected program.', + { enrollmentsLabel }, + )} {' '} {i18n.t('All activity will be logged.')}
- - - - - - -); + + + + + + + + + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Delete/Delete.component.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Delete/Delete.component.tsx index f6dde67b74..00c1b73a70 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Delete/Delete.component.tsx +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Delete/Delete.component.tsx @@ -12,11 +12,13 @@ import { import i18n from '@dhis2/d2-i18n'; import type { Props } from './delete.types'; import { ConditionalTooltip } from '../../../Tooltips/ConditionalTooltip/'; +import { useTermLabel } from '../../../../metaData'; export const Delete = ({ canCascadeDeleteEnrollment, enrollment, onDelete }: Props) => { const [toggle, setToggle] = useState(false); const disabled = !canCascadeDeleteEnrollment; - const tooltipContent = i18n.t('You do not have access to delete this enrollment'); + const enrollmentLabel = useTermLabel('enrollment'); + const tooltipContent = i18n.t('You do not have access to delete this {{enrollmentLabel}}', { enrollmentLabel }); return ( @@ -37,11 +39,11 @@ export const Delete = ({ canCascadeDeleteEnrollment, enrollment, onDelete }: Pro onClose={() => setToggle(false)} dataTest="widget-enrollment-actions-modal" > - {i18n.t('Delete enrollment')} + {i18n.t('Delete {{enrollmentLabel}}', { enrollmentLabel })} - {i18n.t('Are you sure you want to delete this enrollment?')} + {i18n.t('Are you sure you want to delete this {{enrollmentLabel}}?', { enrollmentLabel })} {' '} - {i18n.t('This will permanently remove the current enrollment.')} + {i18n.t('This will permanently remove the current {{enrollmentLabel}}.', { enrollmentLabel })} @@ -52,7 +54,7 @@ export const Delete = ({ canCascadeDeleteEnrollment, enrollment, onDelete }: Pro destructive onClick={() => onDelete(enrollment)} > - {i18n.t('Yes, delete enrollment.')} + {i18n.t('Yes, delete {{enrollmentLabel}}.', { enrollmentLabel })} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx index 0ed8c66bc4..6952202814 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx +++ b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx @@ -12,6 +12,7 @@ import type { TransferModalProps } from './TransferModal.types'; import { OrgUnitField } from './OrgUnitField'; import { useTransferValidation } from './hooks/useTransferValidation'; import { InfoBoxes } from './InfoBoxes'; +import { useTermLabel } from '../../../metaData'; export const TransferModal = ({ enrollment, @@ -20,6 +21,7 @@ export const TransferModal = ({ onUpdateOwnership, isTransferLoading, }: TransferModalProps) => { + const enrollmentLabel = useTermLabel('enrollment', { programId: enrollment.program }); const { selectedOrgUnit, handleOrgUnitChange, @@ -51,7 +53,10 @@ export const TransferModal = ({
- {i18n.t('Choose the organisation unit to which enrollment ownership should be transferred.')} + {i18n.t( + 'Choose the organisation unit to which {{enrollmentLabel}} ownership should be transferred.', + { enrollmentLabel }, + )}
(geometryType === 'Point' ? dataElementTypes.COORDINATE : dataElementTypes.POLYGON); -const getEnrollmentDateLabel = program => program.displayEnrollmentDateLabel ?? i18n.t('Enrollment date'); +const getEnrollmentDateLabel = (program, enrollmentLabel: string) => + program.displayEnrollmentDateLabel ?? i18n.t('{{enrollmentLabel}} date', { enrollmentLabel }); const getIncidentDateLabel = program => program.displayIncidentDateLabel ?? i18n.t('Incident date'); const WidgetEnrollmentPlain = ({ @@ -121,10 +119,12 @@ const WidgetEnrollmentPlain = ({ onClose={useCallback(() => setOpenStatus(false), [setOpenStatus])} open={open} > - {true && ( + {initError && (
- {tLabel('{{enrollmentLabel}} widget could not be loaded. Please try again later', - { enrollmentLabel })} + {tCustomTerm( + '{{enrollmentLabel}} widget could not be loaded. Please try again later', + { enrollmentLabel }, + )}
)} {loading && } @@ -142,7 +142,7 @@ const WidgetEnrollmentPlain = ({
{ const dispatch = useDispatch(); @@ -17,6 +18,7 @@ export const WidgetEnrollmentNote = () => { trackedEntityTypeName, showWidgetBadge, } = useEnrollmentAccessContext(); + const enrollmentLabel = useTermLabel('enrollment'); const onAddNote = (newNoteValue: string) => { dispatch(requestAddNoteForEnrollment(enrollmentId, newNoteValue)); @@ -25,9 +27,9 @@ export const WidgetEnrollmentNote = () => { return (
( - - - {modalState === TEI_MODAL_STATE.OPEN_DISABLE && ( - - )} - {(modalState === TEI_MODAL_STATE.OPEN || modalState === TEI_MODAL_STATE.OPEN_ERROR) && ( - - )} - - } - > - {i18n.t( - 'Change information about this {{trackedEntityName}} here.', - { trackedEntityName, interpolation: { escapeValue: false } }, - )} - {' '} - {i18n.t('Information about this enrollment can be edited in the Enrollment widget.')} - - - -); + {modalState === TEI_MODAL_STATE.OPEN_DISABLE && ( + + )} + {(modalState === TEI_MODAL_STATE.OPEN || modalState === TEI_MODAL_STATE.OPEN_ERROR) && ( + + )} + + } + > + {i18n.t( + 'Change information about this {{trackedEntityName}} here.', + { trackedEntityName, interpolation: { escapeValue: false } }, + )} + {' '} + {i18n.t( + 'Information about this {{enrollmentLabel}} can be edited in the {{enrollmentLabel}} widget.', + { enrollmentLabel }, + )} + + + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/Delete/DeleteModal/DeleteModal.component.tsx b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/Delete/DeleteModal/DeleteModal.component.tsx index 8f7b78883c..c7677de14b 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/Delete/DeleteModal/DeleteModal.component.tsx +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/Delete/DeleteModal/DeleteModal.component.tsx @@ -1,12 +1,15 @@ import React, { useState } from 'react'; import i18n from '@dhis2/d2-i18n'; import { Modal, ModalContent, ModalTitle, ModalActions, ButtonStrip, Button, NoticeBox } from '@dhis2/ui'; +import { useTermLabel } from '../../../../../metaData'; +import { tCustomTerm } from '../../../../../utils/tCustomTerm'; import type { Props } from './DeleteModal.types'; import { useDeleteTrackedEntity } from './hooks'; import type { ErrorReport } from '../../processErrorReports'; export const DeleteModal = ({ trackedEntityTypeName, trackedEntity, setOpenModal, onDeleteSuccess }: Props) => { const [errorReports, setErrorReports] = useState>([]); + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); const handleErrors = (errors: Array) => { setErrorReports(errors); }; @@ -23,10 +26,10 @@ export const DeleteModal = ({ trackedEntityTypeName, trackedEntity, setOpenModal

{/* eslint-disable-next-line max-len */} - {i18n.t('Are you sure you want to delete this {{trackedEntityTypeName}}? This will permanently remove the {{trackedEntityTypeName}} and all its associated enrollments and events in all programs.', + {tCustomTerm('Are you sure you want to delete this {{trackedEntityTypeName}}? This will permanently remove the {{trackedEntityTypeName}} and all its associated {{enrollmentsLabel}} and events in all programs.', { trackedEntityTypeName, - interpolation: { escapeValue: false }, + enrollmentsLabel, }, )}

diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts index 97fae559f4..ada6c9dde0 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts @@ -1,12 +1,7 @@ import { useMemo } from 'react'; import { useDataQuery } from '@dhis2/app-runtime'; -const trackedEntityTypeFields = - 'id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' + - 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' + - 'translations[property,locale,value]'; - -const buildFields = (): string => +const fields = 'id,version,displayName,displayShortName,description,programType,style,minAttributesRequiredToSearch,' + 'enrollmentDateLabel,incidentDateLabel,featureType,selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,' + 'displayIncidentDate,access[*],' + @@ -28,7 +23,9 @@ const buildFields = (): string => 'displayDescription,valueType,optionSetValue,unique,orgunitScope,pattern,translations[property,locale,value],' + 'optionSet[id,displayName,version,valueType,options[id,displayName,name,code,style,translations]]],' + 'displayInList,searchable,mandatory,renderOptionsAsRadio,allowFutureDate],' + - `trackedEntityType[${trackedEntityTypeFields}],` + + 'trackedEntityType[id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' + + 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' + + 'translations[property,locale,value]],' + 'userRoles[id,displayName]'; export const useApiProgram = (programId: string) => { @@ -39,7 +36,7 @@ export const useApiProgram = (programId: string) => { resource: 'programs', id: programId, params: { - fields: buildFields(), + fields, }, }, }), diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/RelationshipsWidget.component.tsx b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/RelationshipsWidget.component.tsx index 7948fbb2ae..33c939c40d 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/RelationshipsWidget.component.tsx +++ b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/RelationshipsWidget.component.tsx @@ -10,6 +10,7 @@ import { LinkedEntitiesViewer } from './LinkedEntitiesViewer.component'; import type { Props } from './relationshipsWidget.types'; import { LoadingMaskElementCenter } from '../../../LoadingMasks'; import { useDeleteRelationship } from './DeleteRelationship/useDeleteRelationship'; +import { useTermLabel } from '../../../../metaData'; const styles = { header: {}, @@ -38,6 +39,7 @@ const RelationshipsWidgetPlain = ({ const [open, setOpenStatus] = useState(true); const groupedLinkedEntities = useGroupedLinkedEntities(sourceId, relationshipTypes, relationships, readOnly); const { onDeleteRelationship } = useDeleteRelationship({ sourceId }); + const enrollmentLabel = useTermLabel('enrollment'); if (isLoading) { return ( @@ -92,7 +94,7 @@ const RelationshipsWidgetPlain = ({ } {(relationships?.length ?? 0) === 0 && (
- {i18n.t("This enrollment doesn't have any relationships")} + {i18n.t("This {{enrollmentLabel}} doesn't have any relationships", { enrollmentLabel })}
)} {children} diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useFiltersOnly.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useFiltersOnly.ts index 0d4b3eaaea..d342f87501 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useFiltersOnly.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useFiltersOnly.ts @@ -1,21 +1,22 @@ import { useMemo } from 'react'; import { featureAvailable, FEATURES } from 'capture-core-utils'; import i18n from '@dhis2/d2-i18n'; -import { dataElementTypes, type TrackerProgram } from '../../../../../metaData'; +import { dataElementTypes, type TrackerProgram, useTermLabel } from '../../../../../metaData'; import { MAIN_FILTERS } from '../../constants'; export const useFiltersOnly = ( { enrollment: { enrollmentDateLabel, incidentDateLabel, showIncidentDate }, stages }: TrackerProgram, programStageId?: string, -) => - useMemo(() => { +) => { + const enrollmentLabel = useTermLabel('enrollment'); + return useMemo(() => { const enableUserAssignment = !programStageId && Array.from(stages.values()).find((stage: any) => stage.enableUserAssignment); return [ { id: MAIN_FILTERS.PROGRAM_STATUS, type: dataElementTypes.TEXT, - header: i18n.t('Enrollment status'), + header: i18n.t('{{enrollmentLabel}} status', { enrollmentLabel }), options: [ { text: i18n.t('Active'), value: 'ACTIVE' }, { text: i18n.t('Completed'), value: 'COMPLETED' }, @@ -96,4 +97,5 @@ export const useFiltersOnly = ( ] : []), ]; - }, [enrollmentDateLabel, incidentDateLabel, showIncidentDate, stages, programStageId]); + }, [enrollmentDateLabel, incidentDateLabel, showIncidentDate, stages, programStageId, enrollmentLabel]); +}; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useStaticTemplates.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useStaticTemplates.ts index 552a9a7653..bd86f2269b 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useStaticTemplates.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/Setup/hooks/useStaticTemplates.ts @@ -1,9 +1,11 @@ import { useMemo } from 'react'; import i18n from '@dhis2/d2-i18n'; import type { WorkingListTemplate } from '../../../WorkingListsBase'; +import { useTermLabel } from '../../../../../metaData'; -export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | undefined, defaultTemplateId: string) => - useMemo( +export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | undefined, defaultTemplateId: string) => { + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); + return useMemo( () => [ defaultAlteredTemplate || { id: defaultTemplateId, @@ -18,7 +20,7 @@ export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | }, { id: 'active', - name: i18n.t('Active enrollments'), + name: i18n.t('Active {{enrollmentsLabel}}', { enrollmentsLabel }), order: 1, access: { update: false, @@ -32,7 +34,7 @@ export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | }, { id: 'complete', - name: i18n.t('Completed enrollments'), + name: i18n.t('Completed {{enrollmentsLabel}}', { enrollmentsLabel }), order: 2, access: { update: false, @@ -46,7 +48,7 @@ export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | }, { id: 'cancelled', - name: i18n.t('Cancelled enrollments'), + name: i18n.t('Cancelled {{enrollmentsLabel}}', { enrollmentsLabel }), order: 3, access: { update: false, @@ -59,5 +61,6 @@ export const useStaticTemplates = (defaultAlteredTemplate: WorkingListTemplate | }, }, ], - [defaultAlteredTemplate, defaultTemplateId], + [defaultAlteredTemplate, defaultTemplateId, enrollmentsLabel], ); +}; diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/CompleteAction.tsx b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/CompleteAction.tsx index be75d43236..ccb0b00f3f 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/CompleteAction.tsx +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/CompleteAction.tsx @@ -16,6 +16,7 @@ import { ConditionalTooltip } from '../../../../../Tooltips/ConditionalTooltip'; import { useCompleteBulkEnrollments } from './hooks/useCompleteBulkEnrollments'; import { Widget } from '../../../../../Widget'; import type { PlainProps } from './CompleteAction.types'; +import { useTermLabel } from '../../../../../../metaData'; const styles: Readonly = { container: { @@ -36,9 +37,13 @@ const styles: Readonly = { }, }; -const getTooltipContent = (programDataWriteAccess: boolean, bulkDataEntryIsActive: boolean) => { +const getTooltipContent = ( + programDataWriteAccess: boolean, + bulkDataEntryIsActive: boolean, + enrollmentsLabel: string, +) => { if (!programDataWriteAccess) { - return i18n.t('You do not have access to bulk complete enrollments'); + return i18n.t('You do not have access to bulk complete {{enrollmentsLabel}}', { enrollmentsLabel }); } if (bulkDataEntryIsActive) { return i18n.t('There is a bulk data entry with unsaved changes'); @@ -59,6 +64,8 @@ const CompleteActionPlain = ({ const [modalIsOpen, setModalIsOpen] = useState(false); const [completeEvents, setCompleteEvents] = useState(true); const [openAccordion, setOpenAccordion] = useState(false); + const enrollmentLabel = useTermLabel('enrollment', { programId }); + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); const { completeEnrollments, enrollmentCounts, @@ -75,7 +82,7 @@ const CompleteActionPlain = ({ onUpdateList, removeRowsFromSelection, }); - const tooltipContent = getTooltipContent(programDataWriteAccess, bulkDataEntryIsActive); + const tooltipContent = getTooltipContent(programDataWriteAccess, bulkDataEntryIsActive, enrollmentsLabel); const disabled = !programDataWriteAccess || bulkDataEntryIsActive; const ModalTextContent = () => { @@ -96,8 +103,12 @@ const CompleteActionPlain = ({ {hasPartiallyUploadedEnrollments ? // eslint-disable-next-line max-len - i18n.t('Some enrollments were completed successfully, but there was an error while completing the rest. Please see the details below.') : - i18n.t('There was an error while completing the enrollments. Please see the details below.') + i18n.t('Some {{enrollmentsLabel}} were completed successfully, but there was an error while completing the rest. Please see the details below.', { enrollmentsLabel }) : + i18n.t( + // eslint-disable-next-line max-len + 'There was an error while completing the {{enrollmentsLabel}}. Please see the details below.', + { enrollmentsLabel }, + ) } @@ -129,7 +140,10 @@ const CompleteActionPlain = ({ if (errorFetchingTrackedEntities) { return (
- {i18n.t('An unexpected error occurred while fetching the enrollments. Please try again.')} + {i18n.t( + 'An unexpected error occurred while fetching the {{enrollmentsLabel}}. Please try again.', + { enrollmentsLabel }, + )}
); } @@ -138,35 +152,41 @@ const CompleteActionPlain = ({ if (enrollmentCounts.active === 0) { return (
- {i18n.t('There are currently no active enrollments in the selection.')} + {i18n.t('There are currently no active {{enrollmentsLabel}} in the selection.', { enrollmentsLabel })} {' '} - {i18n.t('All enrollments are already completed or cancelled.')} + {i18n.t('All {{enrollmentsLabel}} are already completed or cancelled.', { enrollmentsLabel })}
); } return (
- {i18n.t('This action will complete {{count}} active enrollment in your selection.', + {i18n.t('This action will complete {{count}} active {{enrollmentLabel}} in your selection.', { count: enrollmentCounts.active, - defaultValue: 'This action will complete {{count}} active enrollment in your selection.', - defaultValue_plural: 'This action will complete {{count}} active enrollments in your selection.', + enrollmentLabel, + defaultValue: 'This action will complete {{count}} active {{enrollmentLabel}} in your selection.', + // eslint-disable-next-line max-len + defaultValue_plural: 'This action will complete {{count}} active {{enrollmentsLabel}} in your selection.', + enrollmentsLabel, }) } {' '} {enrollmentCounts.completed > 0 && - i18n.t('{{count}} enrollment already marked as completed will not be changed.', { + i18n.t('{{count}} {{enrollmentLabel}} already marked as completed will not be changed.', { count: enrollmentCounts.completed, - defaultValue: '{{count}} enrollment already marked as completed will not be changed.', - defaultValue_plural: '{{count}} enrollments already marked as completed will not be changed.', + enrollmentLabel, + defaultValue: '{{count}} {{enrollmentLabel}} already marked as completed will not be changed.', + // eslint-disable-next-line max-len + defaultValue_plural: '{{count}} {{enrollmentsLabel}} already marked as completed will not be changed.', + enrollmentsLabel, }) } setCompleteEvents(prevState => !prevState)} /> @@ -186,7 +206,7 @@ const CompleteActionPlain = ({ disabled={disabled} onClick={() => setModalIsOpen(true)} > - {i18n.t('Complete enrollments')} + {i18n.t('Complete {{enrollmentsLabel}}', { enrollmentsLabel })} @@ -196,8 +216,8 @@ const CompleteActionPlain = ({ dataTest={'bulk-complete-enrollments-dialog'} > - {validationError ? i18n.t('Error completing enrollments') - : i18n.t('Complete enrollments')} + {validationError ? i18n.t('Error completing {{enrollmentsLabel}}', { enrollmentsLabel }) + : i18n.t('Complete {{enrollmentsLabel}}', { enrollmentsLabel })} @@ -215,7 +235,7 @@ const CompleteActionPlain = ({ {!validationError && ( diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/hooks/useCompleteBulkEnrollments.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/hooks/useCompleteBulkEnrollments.ts index aa7388e5bc..6cbc90e06e 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/hooks/useCompleteBulkEnrollments.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/CompleteAction/hooks/useCompleteBulkEnrollments.ts @@ -7,6 +7,7 @@ import { errorCreator } from 'capture-core-utils'; import { ReactQueryAppNamespace, useApiDataQuery } from '../../../../../../../utils/reactQueryHelpers'; import { handleAPIResponse, REQUESTED_ENTITIES } from '../../../../../../../utils/api'; import type { ProgramStage } from '../../../../../../../metaData'; +import { useTermLabel } from '../../../../../../../metaData'; type Props = { selectedRows: Record; @@ -93,6 +94,7 @@ export const useCompleteBulkEnrollments = ({ ({ message }) => message, { critical: true }, ); + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); const removeQueries = () => { queryClient.removeQueries( @@ -160,7 +162,7 @@ export const useCompleteBulkEnrollments = ({ }, onError: (serverResponse, variables) => { removeQueries(); - showAlert({ message: i18n.t('An error occurred when completing the enrollments') }); + showAlert({ message: i18n.t('An error occurred when completing the {{enrollmentsLabel}}', { enrollmentsLabel }) }); // eslint-disable-line max-len log.error( errorCreator('An error occurred when completing enrollments')({ serverResponse, @@ -185,7 +187,7 @@ export const useCompleteBulkEnrollments = ({ onUpdateList(true); }, onError: (serverResponse, variables) => { - showAlert({ message: i18n.t('An error occurred when completing the enrollments') }); + showAlert({ message: i18n.t('An error occurred when completing the {{enrollmentsLabel}}', { enrollmentsLabel }) }); // eslint-disable-line max-len log.error( errorCreator('An error occurred when completing enrollments')({ serverResponse, @@ -219,7 +221,8 @@ export const useCompleteBulkEnrollments = ({ serverResponse, enrollments, })); - showAlert({ message: i18n.t('An unknown error occurred when completing enrollments') }); + // eslint-disable-next-line max-len + showAlert({ message: i18n.t('An unknown error occurred when completing {{enrollmentsLabel}}', { enrollmentsLabel }) }); return; } const validEnrollments = filterValidEnrollments(enrollments, errors); diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/DeleteEnrollmentsAction.tsx b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/DeleteEnrollmentsAction.tsx index f6d1f5051a..6713804c5f 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/DeleteEnrollmentsAction.tsx +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/DeleteEnrollmentsAction.tsx @@ -5,10 +5,15 @@ import { useAuthority } from '../../../../../../utils/userInfo/useAuthority'; import { EnrollmentDeleteModal } from './EnrollmentDeleteModal'; import { ConditionalTooltip } from '../../../../../Tooltips/ConditionalTooltip'; import type { PlainProps } from './DeleteEnrollmentsAction.types'; +import { useTermLabel } from '../../../../../../metaData'; -const getTooltipContent = (programDataWriteAccess: boolean, bulkDataEntryIsActive: boolean) => { +const getTooltipContent = ( + programDataWriteAccess: boolean, + bulkDataEntryIsActive: boolean, + enrollmentsLabel: string, +) => { if (!programDataWriteAccess) { - return i18n.t('You do not have access to delete enrollments'); + return i18n.t('You do not have access to delete {{enrollmentsLabel}}', { enrollmentsLabel }); } if (bulkDataEntryIsActive) { return i18n.t('There is a bulk data entry with unsaved changes'); @@ -27,7 +32,8 @@ export const DeleteEnrollmentsAction = ({ }: PlainProps) => { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const { hasAuthority } = useAuthority({ authority: CASCADE_DELETE_TEI_AUTHORITY }); - const tooltipContent = getTooltipContent(programDataWriteAccess, bulkDataEntryIsActive); + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); + const tooltipContent = getTooltipContent(programDataWriteAccess, bulkDataEntryIsActive, enrollmentsLabel); const disabled = !programDataWriteAccess || bulkDataEntryIsActive; if (!hasAuthority) { @@ -45,7 +51,7 @@ export const DeleteEnrollmentsAction = ({ disabled={disabled} onClick={() => setIsDeleteDialogOpen(true)} > - {i18n.t('Delete enrollments')} + {i18n.t('Delete {{enrollmentsLabel}}', { enrollmentsLabel })} diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/EnrollmentDeleteModal/EnrollmentDeleteModal.tsx b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/EnrollmentDeleteModal/EnrollmentDeleteModal.tsx index 67b3b227f1..fd6953c0ed 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/EnrollmentDeleteModal/EnrollmentDeleteModal.tsx +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/EnrollmentDeleteModal/EnrollmentDeleteModal.tsx @@ -5,6 +5,7 @@ import i18n from '@dhis2/d2-i18n'; import { useDeleteEnrollments } from '../hooks/useDeleteEnrollments'; import { CustomCheckbox } from './CustomCheckbox'; import type { PlainProps } from './EnrollmentDeleteModal.types'; +import { useTermLabel } from '../../../../../../../metaData'; const styles: Readonly = { modalContent: { @@ -26,6 +27,8 @@ const EnrollmentDeleteModalPlain = ({ setIsDeleteDialogOpen, classes, }: PlainProps & WithStyles) => { + const enrollmentLabel = useTermLabel('enrollment', { programId }); + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); const { deleteEnrollments, isDeletingEnrollments, @@ -49,12 +52,15 @@ const EnrollmentDeleteModalPlain = ({ small > - {i18n.t('Delete selected enrollments')} + {i18n.t('Delete selected {{enrollmentsLabel}}', { enrollmentsLabel })}
- {i18n.t('An error occurred while loading the selected enrollments. Please try again.')} + {i18n.t( + 'An error occurred while loading the selected {{enrollmentsLabel}}. Please try again.', + { enrollmentsLabel }, + )}
@@ -78,7 +84,7 @@ const EnrollmentDeleteModalPlain = ({ onClose={() => setIsDeleteDialogOpen(false)} > - {i18n.t('Delete selected enrollments')} + {i18n.t('Delete selected {{enrollmentsLabel}}', { enrollmentsLabel })} @@ -107,24 +113,24 @@ const EnrollmentDeleteModalPlain = ({ dataTest={'bulk-delete-enrollments-dialog'} > - {i18n.t('Delete selected enrollments')} + {i18n.t('Delete selected {{enrollmentsLabel}}', { enrollmentsLabel })}
{/* eslint-disable-next-line max-len */} - {i18n.t('This action will permanently delete the selected enrollments, including all associated data and events.')} + {i18n.t('This action will permanently delete the selected {{enrollmentsLabel}}, including all associated data and events.', { enrollmentsLabel })}
- {i18n.t('Please select which enrollment statuses you want to delete:')} + {i18n.t('Please select which {{enrollmentLabel}} statuses you want to delete:', { enrollmentLabel })}
- {i18n.t('Delete {{count}} enrollment', { + {i18n.t('Delete {{count}} {{enrollmentLabel}}', { count: numberOfEnrollmentsToDelete, - defaultValue: 'Delete {{count}} enrollment', - defaultValue_plural: 'Delete {{count}} enrollments', + enrollmentLabel, + defaultValue: 'Delete {{count}} {{enrollmentLabel}}', + defaultValue_plural: 'Delete {{count}} {{enrollmentsLabel}}', + enrollmentsLabel, })} diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/hooks/useDeleteEnrollments.ts b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/hooks/useDeleteEnrollments.ts index 0275cf6fc0..fadeb06aba 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/hooks/useDeleteEnrollments.ts +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteEnrollmentsAction/hooks/useDeleteEnrollments.ts @@ -6,6 +6,7 @@ import { useAlert, useDataEngine } from '@dhis2/app-runtime'; import { errorCreator } from 'capture-core-utils'; import { handleAPIResponse, REQUESTED_ENTITIES } from '../../../../../../../utils/api'; import { ReactQueryAppNamespace, useApiDataQuery } from '../../../../../../../utils/reactQueryHelpers'; +import { useTermLabel } from '../../../../../../../metaData'; type Props = { selectedRows: Record; @@ -33,6 +34,7 @@ export const useDeleteEnrollments = ({ ({ message }) => message, { critical: true }, ); + const enrollmentsLabel = useTermLabel('enrollment', { programId, plural: true }); const updateStatusToDelete = useCallback((status: string) => { setStatusToDelete(prevStatus => ({ @@ -81,7 +83,7 @@ export const useDeleteEnrollments = ({ { onError: (error) => { log.error(errorCreator('An error occurred when deleting enrollments')({ error })); - showAlert({ message: i18n.t('An error occurred when deleting enrollments') }); + showAlert({ message: i18n.t('An error occurred when deleting {{enrollmentsLabel}}', { enrollmentsLabel }) }); }, onSuccess: () => { queryClient.removeQueries([ReactQueryAppNamespace, ...QueryKey]); diff --git a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteTeiAction/DeleteTeiAction.tsx b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteTeiAction/DeleteTeiAction.tsx index 44daa45d42..f77db690d5 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteTeiAction/DeleteTeiAction.tsx +++ b/src/core_modules/capture-core/components/WorkingLists/TrackerWorkingLists/TrackedEntityBulkActions/Actions/DeleteTeiAction/DeleteTeiAction.tsx @@ -4,6 +4,8 @@ import { Button, ButtonStrip, Modal, ModalActions, ModalContent, ModalTitle } fr import { useAuthority } from '../../../../../../utils/userInfo/useAuthority'; import { useCascadeDeleteTei } from './hooks/useCascadeDeleteTei'; import type { PlainProps } from './DeleteTeiAction.types'; +import { useTermLabel } from '../../../../../../metaData'; +import { tCustomTerm } from '../../../../../../utils/tCustomTerm'; const CASCADE_DELETE_TEI_AUTHORITY = 'F_TEI_CASCADE_DELETE'; @@ -17,6 +19,7 @@ export const DeleteTeiAction = ({ }: PlainProps) => { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const { hasAuthority } = useAuthority({ authority: CASCADE_DELETE_TEI_AUTHORITY }); + const enrollmentsLabel = useTermLabel('enrollment', { plural: true }); const { deleteTeis, isLoading } = useCascadeDeleteTei({ selectedRows, setIsDeleteDialogOpen, @@ -33,8 +36,9 @@ export const DeleteTeiAction = ({ small onClick={() => setIsDeleteDialogOpen(true)} > - {i18n.t('Delete {{ trackedEntityName }} with all enrollments', { + {tCustomTerm('Delete {{ trackedEntityName }} with all {{enrollmentsLabel}}', { trackedEntityName: trackedEntityName.toLowerCase(), + enrollmentsLabel, })} @@ -53,7 +57,10 @@ export const DeleteTeiAction = ({ - {i18n.t('Deleting records will also delete any associated enrollments and events.')} + {i18n.t( + 'Deleting records will also delete any associated {{enrollmentsLabel}} and events.', + { enrollmentsLabel }, + )} {' '} {i18n.t('This cannot be undone.')} {' '} diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels.ts b/src/core_modules/capture-core/metaData/helpers/customLabels.ts new file mode 100644 index 0000000000..aa7964603b --- /dev/null +++ b/src/core_modules/capture-core/metaData/helpers/customLabels.ts @@ -0,0 +1,117 @@ +import i18n from '@dhis2/d2-i18n'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { programCollection } from '../../metaDataMemoryStores'; + +type LabelConfig = { + field: string; + pluralField?: string; + singular: () => string; + plural?: () => string; +}; + +const asLabels = (labels: Record) => labels; + +const LABELS = asLabels({ + enrollment: { + field: 'displayEnrollmentLabel', + pluralField: 'displayEnrollmentsLabel', + singular: () => i18n.t('enrollment'), + plural: () => i18n.t('enrollments'), + }, + event: { + field: 'displayEventLabel', + pluralField: 'displayEventsLabel', + singular: () => i18n.t('event'), + plural: () => i18n.t('events'), + }, + programStage: { + field: 'displayProgramStageLabel', + pluralField: 'displayProgramStagesLabel', + singular: () => i18n.t('program stage'), + plural: () => i18n.t('program stages'), + }, + note: { + field: 'displayNoteLabel', + singular: () => i18n.t('note'), + }, + relationship: { + field: 'displayRelationshipLabel', + singular: () => i18n.t('relationship'), + }, + attribute: { + field: 'displayTrackedEntityAttributeLabel', + singular: () => i18n.t('attribute'), + }, + orgUnit: { + field: 'displayOrgUnitLabel', + singular: () => i18n.t('organisation unit'), + }, + followUp: { + field: 'displayFollowUpLabel', + singular: () => i18n.t('follow-up'), + }, +}); + +export type CustomLabelKey = keyof typeof LABELS; +export type CustomLabels = Record; +export type LabelOptions = { plural?: boolean }; + +const ALL_FIELD_NAMES = Object.values(LABELS).flatMap( + ({ field, pluralField }) => (pluralField ? [field, pluralField] : [field]), +); + +export const extractCustomLabels = (cached: Record): CustomLabels => + Object.fromEntries( + ALL_FIELD_NAMES + .filter(field => typeof cached[field] === 'string') + .map(field => [field, cached[field] as string]), + ); + +type LabelSource = CustomLabels | undefined | null; + +export const resolveLabel = ( + sources: LabelSource | Array, + key: CustomLabelKey, + { plural = false }: LabelOptions = {}, +): string | undefined => { + const { field, pluralField } = LABELS[key]; + const target = plural ? pluralField : field; + if (!target) return undefined; + const list = Array.isArray(sources) ? sources : [sources]; + return list.find(source => source?.[target])?.[target]; +}; + +type TermLabelOptions = LabelOptions & { stageId?: string; programId?: string }; + +const resolveTerm = ( + programId: string | undefined, + key: CustomLabelKey, + { stageId, plural = false }: TermLabelOptions, +): string => { + const program = programId ? programCollection.get(programId) : undefined; + const stage = program && stageId ? program.getStage(stageId) : undefined; + const custom = resolveLabel([stage?.customLabels, program?.customLabels], key, { plural }); + if (custom) return custom; + if (plural) return LABELS[key].plural?.() ?? `${key}s`; + return LABELS[key].singular(); +}; + +export const getTermLabel = ( + programId: string | undefined, + key: CustomLabelKey, + options: TermLabelOptions = {}, +): string => resolveTerm(programId, key, options); + +export const useTermLabel = ( + key: CustomLabelKey, + options: TermLabelOptions = {}, +): string => { + const { programId, stageId, plural } = options; + const currentProgramId = useSelector(({ currentSelections }: any) => currentSelections.programId); + const id = programId ?? currentProgramId; + return useMemo( + () => resolveTerm(id, key, { stageId, plural }), + [id, key, stageId, plural], + ); +}; diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts deleted file mode 100644 index bf60ed5c72..0000000000 --- a/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts +++ /dev/null @@ -1,51 +0,0 @@ -type CustomLabelField = { - field?: string, - pluralField?: string, -}; - -export const CUSTOM_LABEL_FIELDS = { - enrollment: { field: 'displayEnrollmentLabel', pluralField: 'displayEnrollmentsLabel' }, - event: { field: 'displayEventLabel', pluralField: 'displayEventsLabel' }, - programStage: { field: 'displayProgramStageLabel', pluralField: 'displayProgramStagesLabel' }, - note: { field: 'displayNoteLabel' }, - relationship: { field: 'displayRelationshipLabel' }, - attribute: { field: 'displayTrackedEntityAttributeLabel' }, - orgUnit: { field: 'displayOrgUnitLabel' }, - followUp: { field: 'displayFollowUpLabel' }, -} as const satisfies { [key: string]: CustomLabelField }; - -export type CustomLabelKey = keyof typeof CUSTOM_LABEL_FIELDS; -export type CustomLabels = Record; -export type LabelOptions = { plural?: boolean }; - -const allFields: Array = Array.from( - new Set( - Object.values(CUSTOM_LABEL_FIELDS) - .flatMap((term: CustomLabelField) => [term.field, term.pluralField]) - .filter((field): field is string => Boolean(field)), - ), -); - -export const extractCustomLabels = (cached: Record): CustomLabels => { - const labels: CustomLabels = {}; - allFields.forEach((field) => { - if (cached[field]) labels[field] = cached[field]; - }); - return labels; -}; - -type LabelSource = CustomLabels | undefined | null; - -export const resolveLabel = ( - sources: LabelSource | Array, - key: CustomLabelKey, - { plural = false }: LabelOptions = {}, -): string | undefined => { - const term = CUSTOM_LABEL_FIELDS[key] as CustomLabelField; - const list = Array.isArray(sources) ? sources : [sources]; - const pick = (field?: string) => (field ? list.find(s => s?.[field])?.[field] : undefined); - if (plural && term.pluralField) { - return pick(term.pluralField); - } - return pick(term.field); -}; diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts deleted file mode 100644 index 9c9de5821a..0000000000 --- a/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { CUSTOM_LABEL_FIELDS, extractCustomLabels, resolveLabel } from './customLabels'; -export type { CustomLabelKey, CustomLabels, LabelOptions } from './customLabels'; -export { getTermLabel, useTermLabel } from './useLabel'; -export { tLabel } from './tLabel'; -export { capitalizeFirstLetter } from '../../../utils/capitalizeFirstLetter'; diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/tLabel.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/tLabel.ts deleted file mode 100644 index 172b02b9c5..0000000000 --- a/src/core_modules/capture-core/metaData/helpers/customLabels/tLabel.ts +++ /dev/null @@ -1,30 +0,0 @@ -import i18n from '@dhis2/d2-i18n'; -import { capitalizeFirstLetter } from '../../../utils/capitalizeFirstLetter'; - -const getRawTranslation = (key: string): string => - (i18n as any).getResource((i18n as any).language, 'default', key) - ?? (i18n as any).getResource('en', 'default', key) - ?? key; - -const startsWithVar = (raw: string, varName: string): boolean => { - const trimmed = raw.trimStart(); - return trimmed.startsWith(`{{${varName}}}`) || trimmed.startsWith(`{{${varName},`); -}; - -/** - * Drop-in replacement for i18n.t() when the string contains custom label variables. - * Automatically capitalizes a variable's value when it appears as the first word - * in the translated string — without requiring any changes to translation files. - */ -export const tLabel = (key: string, options: Record = {}): string => { - const raw = getRawTranslation(key); - const processedOptions = { ...options }; - - for (const [varName, value] of Object.entries(options)) { - if (typeof value === 'string' && startsWithVar(raw, varName)) { - processedOptions[varName] = capitalizeFirstLetter(value); - } - } - - return i18n.t(key, { ...processedOptions, interpolation: { escapeValue: false } }); -}; diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/useLabel.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/useLabel.ts deleted file mode 100644 index dae529d57f..0000000000 --- a/src/core_modules/capture-core/metaData/helpers/customLabels/useLabel.ts +++ /dev/null @@ -1,58 +0,0 @@ -import i18n from '@dhis2/d2-i18n'; -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { programCollection } from '../../../metaDataMemoryStores'; -import { resolveLabel } from './customLabels'; -import type { CustomLabelKey, LabelOptions } from './customLabels'; - -const defaults: Record string> = { - enrollment: () => i18n.t('enrollment'), - event: () => i18n.t('event'), - programStage: () => i18n.t('program stage'), - note: () => i18n.t('note'), - relationship: () => i18n.t('relationship'), - attribute: () => i18n.t('attribute'), - orgUnit: () => i18n.t('organisation unit'), - followUp: () => i18n.t('follow-up'), -}; - -type TermLabelOptions = LabelOptions & { stageId?: string }; - -const resolve = ( - programId: string | undefined, - key: CustomLabelKey, - { stageId, plural }: TermLabelOptions, -): string => { - const program = programId ? programCollection.get(programId) : undefined; - const stage = program && stageId ? program.getStage(stageId) : undefined; - const customLabel = resolveLabel([stage?.customLabels, program?.customLabels], key, { plural }); - return customLabel ?? defaults[key](); -}; - -/** - * Works anywhere — components, reducers, epics. - * Returns the custom label from the program (or stage), falling back to the - * translated default term. - */ -export const getTermLabel = ( - programId: string | undefined, - key: CustomLabelKey, - options: TermLabelOptions = {}, -): string => resolve(programId, key, options); - -/** - * React hook version — reads programId from Redux automatically. - * Pass programId explicitly to override (e.g. cross-program widgets). - */ -export const useTermLabel = ( - key: CustomLabelKey, - options: TermLabelOptions & { programId?: string } = {}, -): string => { - const { programId, stageId, plural } = options; - const currentProgramId = useSelector(({ currentSelections }: any) => currentSelections.programId); - const id = programId ?? currentProgramId; - return useMemo( - () => resolve(id, key, { stageId, plural }), - [id, key, stageId, plural], - ); -}; diff --git a/src/core_modules/capture-core/metaData/helpers/index.ts b/src/core_modules/capture-core/metaData/helpers/index.ts index 94a88e45ee..b4158ea1a9 100644 --- a/src/core_modules/capture-core/metaData/helpers/index.ts +++ b/src/core_modules/capture-core/metaData/helpers/index.ts @@ -18,12 +18,9 @@ export { getProgramEventAccess } from './getProgramEventAccess'; export { getProgramAndStageForProgram } from './getProgramAndStageForProgram'; export { getProgramThrowIfNotFound } from './getProgramThrowIfNotFound'; export { - CUSTOM_LABEL_FIELDS, extractCustomLabels, resolveLabel, getTermLabel, useTermLabel, - tLabel, - capitalizeFirstLetter, } from './customLabels'; export type { CustomLabelKey, CustomLabels, LabelOptions } from './customLabels'; diff --git a/src/core_modules/capture-core/metaData/index.ts b/src/core_modules/capture-core/metaData/index.ts index a098e321b4..1e8e7461b6 100644 --- a/src/core_modules/capture-core/metaData/index.ts +++ b/src/core_modules/capture-core/metaData/index.ts @@ -40,12 +40,9 @@ export { getProgramThrowIfNotFound, getProgramAndStageForEventProgram, getEventProgramEventAccess, - CUSTOM_LABEL_FIELDS, extractCustomLabels, resolveLabel, getTermLabel, useTermLabel, - tLabel, - capitalizeFirstLetter, } from './helpers'; export type { CustomLabelKey, CustomLabels, LabelOptions } from './helpers'; diff --git a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.ts b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.ts index b03059cd59..9b3b3e93a0 100644 --- a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.ts +++ b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.ts @@ -5,6 +5,8 @@ import isString from 'd2-utilizr/lib/isString'; import isObject from 'd2-utilizr/lib/isObject'; import uuid from 'd2-utilizr/lib/uuid'; import { errorCreator } from 'capture-core-utils'; +import { getTermLabel } from '../../metaData'; +import { tCustomTerm } from '../../utils/tCustomTerm'; import { createReducerDescription } from '../../trackerRedux/trackerReducer'; import { actionTypes as feedbackActionTypes } from '../../components/FeedbackBar/actions/feedback.actions'; import { actionTypes as dataEntryActionTypes } from '../../components/DataEntry/actions/dataEntry.actions'; @@ -109,12 +111,20 @@ export const getFeedbackDesc = (appUpdaters: Updaters) => createReducerDescripti addErrorFeedback({ message: i18n.t('Organisation unit search failed.') }), [registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_SAVE_FAILED]: () => addErrorFeedback({ message: i18n.t('Error saving tracked entity instance') }), - [registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_FAILED]: () => - addErrorFeedback({ message: i18n.t('Error saving enrollment') }), - [enrollmentSiteActionTypes.SAVE_FAILED]: () => - addErrorFeedback({ message: i18n.t('Error saving the enrollment event') }), - [editEventActionTypes.DELETE_EVENT_DATA_ENTRY_FAILED]: () => - addErrorFeedback({ message: i18n.t('Error deleting the enrollment event') }), + [registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_FAILED]: () => { + const enrollmentLabel = getTermLabel(undefined, 'enrollment'); + return addErrorFeedback({ message: tCustomTerm('Error saving {{enrollmentLabel}}', { enrollmentLabel }) }); + }, + [enrollmentSiteActionTypes.SAVE_FAILED]: () => { + const enrollmentLabel = getTermLabel(undefined, 'enrollment'); + return addErrorFeedback({ message: tCustomTerm('Error saving the {{enrollmentLabel}} event', { enrollmentLabel }) }); + }, + [editEventActionTypes.DELETE_EVENT_DATA_ENTRY_FAILED]: () => { + const enrollmentLabel = getTermLabel(undefined, 'enrollment'); + return addErrorFeedback({ + message: tCustomTerm('Error deleting the {{enrollmentLabel}} event', { enrollmentLabel }), + }); + }, [editEventDataEntryAction.SAVE_EDIT_EVENT_DATA_ENTRY_FAILED]: () => addErrorFeedback({ message: i18n.t('Error editing the event, the changes made were not saved') }), [enrollmentSiteActionTypes.ERROR_ENROLLMENT]: (_state, action) => @@ -123,8 +133,10 @@ export const getFeedbackDesc = (appUpdaters: Updaters) => createReducerDescripti addErrorFeedback({ message: i18n.t('Error updating the Assignee') }), [enrollmentEditEventActionTypes.ASSIGNEE_SAVE_FAILED]: () => addErrorFeedback({ message: i18n.t('Error updating the Assignee') }), - [enrollmentNoteActionTypes.ADD_NOTE_FAILED_FOR_ENROLLMENT]: () => - addErrorFeedback({ message: i18n.t('Could not save enrollment note') }), + [enrollmentNoteActionTypes.ADD_NOTE_FAILED_FOR_ENROLLMENT]: () => { + const enrollmentLabel = getTermLabel(undefined, 'enrollment'); + return addErrorFeedback({ message: tCustomTerm('Could not save {{enrollmentLabel}} note', { enrollmentLabel }) }); + }, [eventNoteActionTypes.ADD_NOTE_FAILED_FOR_EVENT]: () => addErrorFeedback({ message: i18n.t('Could not save event note') }), [viewEventNotesActionTypes.SAVE_EVENT_NOTE_FAILED]: () => diff --git a/src/core_modules/capture-core/utils/tCustomTerm.ts b/src/core_modules/capture-core/utils/tCustomTerm.ts new file mode 100644 index 0000000000..dcef43f076 --- /dev/null +++ b/src/core_modules/capture-core/utils/tCustomTerm.ts @@ -0,0 +1,37 @@ +import i18n from '@dhis2/d2-i18n'; +import { capitalizeFirstLetter } from './capitalizeFirstLetter'; + +type I18nInternal = { + getResource: (lang: string, ns: string, key: string) => string | undefined; + language: string; +}; +const internal = i18n as unknown as I18nInternal; + +const getRawTranslation = (key: string): string => + internal.getResource(internal.language, 'default', key) + ?? internal.getResource('en', 'default', key) + ?? key; + +const startsWithVar = (raw: string, varName: string): boolean => { + const trimmed = raw.trimStart(); + return trimmed.startsWith(`{{${varName}}}`) || trimmed.startsWith(`{{${varName},`); +}; + +export const tCustomTerm = (key: string, options: Record = {}): string => { + const raw = getRawTranslation(key); + const { interpolation, ...values } = options; + + const cased = Object.fromEntries( + Object.entries(values).map(([name, value]) => [ + name, + typeof value === 'string' && startsWithVar(raw, name) + ? capitalizeFirstLetter(value) + : value, + ]), + ); + + return i18n.t(key, { + ...cased, + interpolation: { escapeValue: false, ...(interpolation as Record ?? {}) }, + }); +};