- {displayDueDateLabel ?? i18n.t('Schedule date / Due date', {
+ {displayDueDateLabel ? capitalizeFirstLetter(displayDueDateLabel) : i18n.t('Schedule date / Due date', {
interpolation: { escapeValue: false },
},
)}
diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx
index 2b99d00290..7803a97d33 100644
--- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx
+++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.tsx
@@ -181,7 +181,7 @@ export const WidgetEventSchedule = ({
if (!program || !stage || !(program instanceof TrackerProgram) || !programStageScheduleConfig) {
return (
- {i18n.t('Program or stage is invalid')}
+ {i18n.t('Program or program stage is invalid')}
);
}
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 ada6c9dde0..97fae559f4 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts
+++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useApiProgram.ts
@@ -1,7 +1,12 @@
import { useMemo } from 'react';
import { useDataQuery } from '@dhis2/app-runtime';
-const fields =
+const trackedEntityTypeFields =
+ 'id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' +
+ 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' +
+ 'translations[property,locale,value]';
+
+const buildFields = (): string =>
'id,version,displayName,displayShortName,description,programType,style,minAttributesRequiredToSearch,' +
'enrollmentDateLabel,incidentDateLabel,featureType,selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,' +
'displayIncidentDate,access[*],' +
@@ -23,9 +28,7 @@ const fields =
'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[id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,' +
- 'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' +
- 'translations[property,locale,value]],' +
+ `trackedEntityType[${trackedEntityTypeFields}],` +
'userRoles[id,displayName]';
export const useApiProgram = (programId: string) => {
@@ -36,7 +39,7 @@ export const useApiProgram = (programId: string) => {
resource: 'programs',
id: programId,
params: {
- fields,
+ fields: buildFields(),
},
},
}),
diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTeiDisplayName.ts b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTeiDisplayName.ts
index 0b8f9ec300..dec5ad533c 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useTeiDisplayName.ts
+++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useTeiDisplayName.ts
@@ -2,8 +2,6 @@ import { useMemo } from 'react';
import i18n from '@dhis2/d2-i18n';
import { convertClientToView } from '../DataEntry';
-const DEFAULT_NAME = i18n.t('tracked entity instance');
-
type TeiAttribute = {
attribute: string;
value?: string;
@@ -49,7 +47,7 @@ const deriveTeiName = (
tetAttributes: TetAttribute[],
teiId?: string,
) => {
- if (!attributes || !tetAttributes) return teiId ?? DEFAULT_NAME;
+ if (!attributes || !tetAttributes) return teiId ?? i18n.t('tracked entity instance');
const teiNameDisplayInList = getTetAttributesDisplayInList(attributes, tetAttributes as TetAttribute[]);
if (teiNameDisplayInList) return teiNameDisplayInList;
@@ -57,7 +55,7 @@ const deriveTeiName = (
const teiName = getTetAttributes(attributes, tetAttributes);
if (teiName) return teiName;
- return teiId ?? DEFAULT_NAME;
+ return teiId ?? i18n.t('tracked entity instance');
};
export const useTeiDisplayName = (
diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useStageLabels.ts b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useStageLabels.ts
index 2cdbd0a2ee..2955ca81c8 100644
--- a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useStageLabels.ts
+++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useStageLabels.ts
@@ -1,4 +1,5 @@
import i18n from '@dhis2/d2-i18n';
+import { capitalizeFirstLetter } from 'capture-core-utils/string/capitalizeFirstLetter';
import { getUserMetadataStorageController, USER_METADATA_STORES } from '../../../storageControllers';
import { useIndexedDBQuery } from '../../../utils/reactQueryHelpers';
@@ -23,8 +24,12 @@ export const useStageLabels = (programId: string, programStageId?: string) => {
);
return {
- scheduledLabel: data?.displayDueDateLabel ?? i18n.t('Scheduled date'),
- occurredLabel: data?.displayExecutionDateLabel ?? i18n.t('Report date'),
+ scheduledLabel: data?.displayDueDateLabel
+ ? capitalizeFirstLetter(data.displayDueDateLabel)
+ : i18n.t('Scheduled date'),
+ occurredLabel: data?.displayExecutionDateLabel
+ ? capitalizeFirstLetter(data.displayExecutionDateLabel)
+ : i18n.t('Report date'),
isLoading: isInitialLoading,
error,
};
diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageCreateNewButton/StageCreateNewButton.tsx b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageCreateNewButton/StageCreateNewButton.tsx
index 891650a162..f1bd8bea3c 100644
--- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageCreateNewButton/StageCreateNewButton.tsx
+++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageCreateNewButton/StageCreateNewButton.tsx
@@ -31,7 +31,7 @@ export const StageCreateNewButton = ({
if (!repeatable && eventCount > 0) {
return {
isDisabled: true,
- tooltipContent: i18n.t('This stage can only have one event'),
+ tooltipContent: i18n.t('This program stage can only have one event'),
};
}
return {
diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stages.component.tsx b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stages.component.tsx
index 6ee363c119..bca2944634 100644
--- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stages.component.tsx
+++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stages.component.tsx
@@ -52,7 +52,7 @@ export const StagesPlain = ({
if (!readableStages.length) {
return (
- {i18n.t('No stages found in this program')}
+ {i18n.t('No program stages found in this program')}
);
}
diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/WidgetStagesAndEvents.component.tsx b/src/core_modules/capture-core/components/WidgetStagesAndEvents/WidgetStagesAndEvents.component.tsx
index 6a410d71ff..13a96518fc 100644
--- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/WidgetStagesAndEvents.component.tsx
+++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/WidgetStagesAndEvents.component.tsx
@@ -44,7 +44,7 @@ const WidgetStagesAndEventsPlain = ({
- {i18n.t('Stages and Events')}
+ {i18n.t('Program stages and events')}
{showWidgetBadge && (
{
+ const statusLabels: Record = {
+ ACTIVE: i18n.t('Active'),
+ COMPLETED: i18n.t('Completed'),
+ CANCELLED: i18n.t('Cancelled'),
+ SCHEDULE: i18n.t('Scheduled'),
+ };
-const DataEntryFieldsToInclude = {
- occurredAt: {
- apiKey: 'occurredAt',
- type: dataElementTypes.DATE,
- placement: Placements.TOP,
- },
- scheduledAt: {
- apiKey: 'scheduledAt',
- type: dataElementTypes.DATE,
- placement: Placements.TOP,
- },
- orgUnit: {
- apiKey: 'orgUnit',
- type: dataElementTypes.ORGANISATION_UNIT,
- placement: Placements.TOP,
- label: i18n.t('Organisation unit'),
- convertFn: (orgUnitId: string) => React.createElement(TooltipOrgUnit, { orgUnitId }),
- },
- status: {
- apiKey: 'status',
- type: dataElementTypes.TEXT,
- placement: Placements.BOTTOM,
- label: i18n.t('Status'),
- convertFn: (value: keyof typeof StatusLabels) => StatusLabels[value],
- },
-};
+ const dataEntryFieldsToInclude = {
+ occurredAt: {
+ apiKey: 'occurredAt',
+ type: dataElementTypes.DATE,
+ placement: Placements.TOP,
+ },
+ scheduledAt: {
+ apiKey: 'scheduledAt',
+ type: dataElementTypes.DATE,
+ placement: Placements.TOP,
+ },
+ orgUnit: {
+ apiKey: 'orgUnit',
+ type: dataElementTypes.ORGANISATION_UNIT,
+ placement: Placements.TOP,
+ label: i18n.t('Organisation unit'),
+ convertFn: (orgUnitId: string) => React.createElement(TooltipOrgUnit, { orgUnitId }),
+ },
+ status: {
+ apiKey: 'status',
+ type: dataElementTypes.TEXT,
+ placement: Placements.BOTTOM,
+ label: i18n.t('Status'),
+ convertFn: (value: string) => statusLabels[value],
+ },
+ };
-export const getDataEntryDetails = (linkedEvent: LinkedEvent, formFoundation: RenderFoundation) => {
- const dataEntryValues = Object.values(DataEntryFieldsToInclude).map((entry: any) => {
+ const dataEntryValues = Object.values(dataEntryFieldsToInclude).map((entry: any) => {
const value = linkedEvent[entry.apiKey];
if (!value) return null;
diff --git a/src/core_modules/capture-core/metaData/TrackedEntityType/TrackedEntityType.ts b/src/core_modules/capture-core/metaData/TrackedEntityType/TrackedEntityType.ts
index 8457d90378..c037bb2ad1 100644
--- a/src/core_modules/capture-core/metaData/TrackedEntityType/TrackedEntityType.ts
+++ b/src/core_modules/capture-core/metaData/TrackedEntityType/TrackedEntityType.ts
@@ -6,7 +6,6 @@ import type { SearchGroup } from '../SearchGroup';
import type { DataElement } from '../DataElement';
import type { TeiRegistration } from './TeiRegistration';
import type { Access } from '../Access';
-import type { CustomLabels } from '../helpers/customLabels';
export class TrackedEntityType {
_id!: string;
@@ -15,11 +14,9 @@ export class TrackedEntityType {
_teiRegistration!: TeiRegistration;
_attributes!: Array;
_searchGroups!: Array;
- _customLabels!: CustomLabels;
constructor(initFn: ((_this: TrackedEntityType) => void) | null) {
this._attributes = [];
- this._customLabels = {};
initFn && isFunction(initFn) && initFn(this);
}
@@ -64,11 +61,4 @@ export class TrackedEntityType {
get attributes(): Array {
return this._attributes;
}
-
- set customLabels(customLabels: CustomLabels) {
- this._customLabels = customLabels;
- }
- get customLabels(): CustomLabels {
- return this._customLabels;
- }
}
diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts
index 18938840cd..bf60ed5c72 100644
--- a/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts
+++ b/src/core_modules/capture-core/metaData/helpers/customLabels/customLabels.ts
@@ -5,14 +5,13 @@ type CustomLabelField = {
export const CUSTOM_LABEL_FIELDS = {
enrollment: { field: 'displayEnrollmentLabel', pluralField: 'displayEnrollmentsLabel' },
- followUp: { field: 'displayFollowUpLabel' },
- orgUnit: { field: 'displayOrgUnitLabel' },
- relationship: { field: 'displayRelationshipLabel' },
+ event: { field: 'displayEventLabel', pluralField: 'displayEventsLabel' },
+ programStage: { field: 'displayProgramStageLabel', pluralField: 'displayProgramStagesLabel' },
note: { field: 'displayNoteLabel' },
+ relationship: { field: 'displayRelationshipLabel' },
attribute: { field: 'displayTrackedEntityAttributeLabel' },
- programStage: { field: 'displayProgramStageLabel', pluralField: 'displayProgramStagesLabel' },
- event: { field: 'displayEventLabel', pluralField: 'displayEventsLabel' },
- trackedEntityType: { pluralField: 'displayTrackedEntityTypesLabel' },
+ orgUnit: { field: 'displayOrgUnitLabel' },
+ followUp: { field: 'displayFollowUpLabel' },
} as const satisfies { [key: string]: CustomLabelField };
export type CustomLabelKey = keyof typeof CUSTOM_LABEL_FIELDS;
@@ -30,9 +29,7 @@ const allFields: Array = Array.from(
export const extractCustomLabels = (cached: Record): CustomLabels => {
const labels: CustomLabels = {};
allFields.forEach((field) => {
- if (cached[field]) {
- labels[field] = cached[field];
- }
+ if (cached[field]) labels[field] = cached[field];
});
return labels;
};
@@ -44,30 +41,11 @@ export const resolveLabel = (
key: CustomLabelKey,
{ plural = false }: LabelOptions = {},
): string | undefined => {
- const term: CustomLabelField = CUSTOM_LABEL_FIELDS[key];
+ const term = CUSTOM_LABEL_FIELDS[key] as CustomLabelField;
const list = Array.isArray(sources) ? sources : [sources];
- const pick = (field?: string) => (field ? list.find(source => source?.[field])?.[field] : undefined);
-
- if (plural) {
- return term.pluralField ? pick(term.pluralField) : pick(term.field);
+ const pick = (field?: string) => (field ? list.find(s => s?.[field])?.[field] : undefined);
+ if (plural && term.pluralField) {
+ return pick(term.pluralField);
}
return pick(term.field);
};
-
-type WithLabels = { customLabels?: CustomLabels } | undefined | null;
-
-export const getProgramLabel = (program: WithLabels, key: CustomLabelKey, options?: LabelOptions): string | undefined =>
- resolveLabel(program?.customLabels, key, options);
-
-export const getStageLabel = (
- stage: WithLabels,
- program: WithLabels,
- key: CustomLabelKey,
- options?: LabelOptions,
-): string | undefined => resolveLabel([stage?.customLabels, program?.customLabels], key, options);
-
-export const getTrackedEntityTypeLabel = (
- trackedEntityType: WithLabels,
- key: CustomLabelKey,
- options?: LabelOptions,
-): string | undefined => resolveLabel(trackedEntityType?.customLabels, key, options);
diff --git a/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts b/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts
index 49b34132fe..9c9de5821a 100644
--- a/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts
+++ b/src/core_modules/capture-core/metaData/helpers/customLabels/index.ts
@@ -1,10 +1,5 @@
-export {
- CUSTOM_LABEL_FIELDS,
- resolveLabel,
- extractCustomLabels,
- getProgramLabel,
- getStageLabel,
- getTrackedEntityTypeLabel,
-} from './customLabels';
+export { CUSTOM_LABEL_FIELDS, extractCustomLabels, resolveLabel } from './customLabels';
export type { CustomLabelKey, CustomLabels, LabelOptions } from './customLabels';
-export { useProgramLabel, useStageLabel, useTrackedEntityTypeLabel } from './useLabel';
+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
new file mode 100644
index 0000000000..172b02b9c5
--- /dev/null
+++ b/src/core_modules/capture-core/metaData/helpers/customLabels/tLabel.ts
@@ -0,0 +1,30 @@
+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
index c733c2e662..dae529d57f 100644
--- a/src/core_modules/capture-core/metaData/helpers/customLabels/useLabel.ts
+++ b/src/core_modules/capture-core/metaData/helpers/customLabels/useLabel.ts
@@ -1,45 +1,58 @@
+import i18n from '@dhis2/d2-i18n';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
-import { programCollection, trackedEntityTypesCollection } from '../../../metaDataMemoryStores';
+import { programCollection } from '../../../metaDataMemoryStores';
import { resolveLabel } from './customLabels';
import type { CustomLabelKey, LabelOptions } from './customLabels';
-type ProgramOptions = LabelOptions & { programId?: string };
-type StageOptions = LabelOptions & { programId?: string, stageId?: string };
-type TrackedEntityTypeOptions = LabelOptions & { tetId?: string };
-
-export const useProgramLabel = (key: CustomLabelKey, { programId, plural }: ProgramOptions = {}): string | undefined => {
- const currentProgramId = useSelector(({ currentSelections }: any) => currentSelections.programId);
- const id = programId ?? currentProgramId;
- return useMemo(
- () => resolveLabel(id ? programCollection.get(id)?.customLabels : undefined, key, { plural }),
- [id, key, plural],
- );
+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'),
};
-export const useStageLabel = (
+type TermLabelOptions = LabelOptions & { stageId?: string };
+
+const resolve = (
+ programId: string | undefined,
key: CustomLabelKey,
- { programId, stageId, plural }: StageOptions = {},
-): string | undefined => {
- const currentProgramId = useSelector(({ currentSelections }: any) => currentSelections.programId);
- const currentStageId = useSelector(({ currentSelections }: any) => currentSelections.stageId);
- const pId = programId ?? currentProgramId;
- const sId = stageId ?? currentStageId;
- return useMemo(() => {
- const program = pId ? programCollection.get(pId) : undefined;
- const stage = program && sId ? program.getStage(sId) : undefined;
- return resolveLabel([stage?.customLabels, program?.customLabels], key, { plural });
- }, [pId, sId, key, plural]);
+ { 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]();
};
-export const useTrackedEntityTypeLabel = (
+/**
+ * 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,
- { tetId, plural }: TrackedEntityTypeOptions = {},
-): string | undefined => {
- const currentTetId = useSelector(({ currentSelections }: any) => currentSelections.trackedEntityTypeId);
- const id = tetId ?? currentTetId;
+ options: TermLabelOptions & { programId?: string } = {},
+): string => {
+ const { programId, stageId, plural } = options;
+ const currentProgramId = useSelector(({ currentSelections }: any) => currentSelections.programId);
+ const id = programId ?? currentProgramId;
return useMemo(
- () => resolveLabel(id ? trackedEntityTypesCollection.get(id)?.customLabels : undefined, key, { plural }),
- [id, key, plural],
+ () => 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 627adbd3b3..94a88e45ee 100644
--- a/src/core_modules/capture-core/metaData/helpers/index.ts
+++ b/src/core_modules/capture-core/metaData/helpers/index.ts
@@ -19,13 +19,11 @@ export { getProgramAndStageForProgram } from './getProgramAndStageForProgram';
export { getProgramThrowIfNotFound } from './getProgramThrowIfNotFound';
export {
CUSTOM_LABEL_FIELDS,
- resolveLabel,
extractCustomLabels,
- getProgramLabel,
- getStageLabel,
- getTrackedEntityTypeLabel,
- useProgramLabel,
- useStageLabel,
- useTrackedEntityTypeLabel,
+ 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 00e7aca7aa..a098e321b4 100644
--- a/src/core_modules/capture-core/metaData/index.ts
+++ b/src/core_modules/capture-core/metaData/index.ts
@@ -41,13 +41,11 @@ export {
getProgramAndStageForEventProgram,
getEventProgramEventAccess,
CUSTOM_LABEL_FIELDS,
- resolveLabel,
extractCustomLabels,
- getProgramLabel,
- getStageLabel,
- getTrackedEntityTypeLabel,
- useProgramLabel,
- useStageLabel,
- useTrackedEntityTypeLabel,
+ resolveLabel,
+ getTermLabel,
+ useTermLabel,
+ tLabel,
+ capitalizeFirstLetter,
} from './helpers';
export type { CustomLabelKey, CustomLabels, LabelOptions } from './helpers';
diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/programStage/ProgramStageFactory.ts b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/programStage/ProgramStageFactory.ts
index 112aa41d14..5c56c02152 100644
--- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/programStage/ProgramStageFactory.ts
+++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/programStage/ProgramStageFactory.ts
@@ -239,8 +239,16 @@ export class ProgramStageFactory {
_form.description = cachedProgramStage.description;
_form.featureType = ProgramStageFactory._getFeatureType(cachedProgramStage);
_form.access = cachedProgramStage.access;
- _form.addLabel({ id: 'occurredAt', label: cachedProgramStage.displayExecutionDateLabel || 'Report date' });
- _form.addLabel({ id: 'scheduledAt', label: cachedProgramStage.displayDueDateLabel || 'Scheduled date' });
+ const executionLabel = cachedProgramStage.displayExecutionDateLabel;
+ const dueDateLabel = cachedProgramStage.displayDueDateLabel;
+ _form.addLabel({
+ id: 'occurredAt',
+ label: executionLabel ? capitalizeFirstLetter(executionLabel) : 'Report date',
+ });
+ _form.addLabel({
+ id: 'scheduledAt',
+ label: dueDateLabel ? capitalizeFirstLetter(dueDateLabel) : 'Scheduled date',
+ });
_form.validationStrategy =
cachedProgramStage.validationStrategy &&
camelCaseUppercaseString(cachedProgramStage.validationStrategy);
diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/TrackedEntityTypeFactory.ts b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/TrackedEntityTypeFactory.ts
index 5513dce0ee..fb8c442ede 100644
--- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/TrackedEntityTypeFactory.ts
+++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/TrackedEntityTypeFactory.ts
@@ -1,8 +1,5 @@
/* eslint-disable no-underscore-dangle */
-import {
- TrackedEntityType,
- extractCustomLabels,
-} from '../../../../metaData';
+import { TrackedEntityType } from '../../../../metaData';
import { DataElementFactory } from './DataElementFactory';
import { TeiRegistrationFactory } from './TeiRegistrationFactory';
import { SearchGroupFactory } from '../../../common/factory';
@@ -84,7 +81,6 @@ export class TrackedEntityTypeFactory {
o.name = this._getTranslation(
cachedType.translations, TrackedEntityTypeFactory.translationPropertyNames.NAME)
|| cachedType.displayName;
- o.customLabels = extractCustomLabels(cachedType);
});
if (cachedType.trackedEntityTypeAttributes) {
diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts
index 929645433c..7d972501b6 100644
--- a/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts
+++ b/src/core_modules/capture-core/metaDataStoreLoaders/programs/quickStoreOperations/storePrograms.ts
@@ -1,3 +1,4 @@
+import { FEATURES, featureAvailable } from 'capture-core-utils/featuresSupport';
import { quickStore } from '../../IOUtils';
import { getContext } from '../../context';
import type { CachedProgramStageDataElement } from '../../../storageControllers';
@@ -97,7 +98,7 @@ const programTrackedEntityAttributeFields = [
'allowFutureDate',
].join(',');
-const programStageFields = [
+const baseProgramStageFields = [
'id',
'access',
'autoGenerateEvent',
@@ -117,7 +118,6 @@ const programStageFields = [
'displayDueDateLabel',
'displayProgramStageLabel',
'displayEventLabel',
- 'displayEventsLabel',
'formType',
'featureType',
'validationStrategy',
@@ -126,9 +126,13 @@ const programStageFields = [
'dataEntryForm[id,htmlCode]',
'programStageSections[id,displayName,displayDescription,sortOrder,dataElements[id]]',
`programStageDataElements[${programStageDataElementFields}]`,
-].join(',');
+];
+
+const pluralProgramStageFields = [
+ 'displayEventsLabel',
+];
-const fieldsParam = [
+const baseProgramFields = [
'id',
'displayName',
'displayShortName',
@@ -138,16 +142,13 @@ const fieldsParam = [
'displayIncidentDateLabel',
'displayEnrollmentDateLabel',
'displayEnrollmentLabel',
- 'displayEnrollmentsLabel',
'displayFollowUpLabel',
'displayOrgUnitLabel',
'displayRelationshipLabel',
'displayNoteLabel',
'displayTrackedEntityAttributeLabel',
'displayProgramStageLabel',
- 'displayProgramStagesLabel',
'displayEventLabel',
- 'displayEventsLabel',
'minAttributesRequiredToSearch',
'useFirstStageDuringRegistration',
'onlyEnrollOnce',
@@ -163,16 +164,35 @@ const fieldsParam = [
'access[data[read,write]]',
'trackedEntityType[id]',
'categoryCombo[id,displayName,isDefault,categories[id,displayName]]',
- `programStages[${programStageFields}]`,
'programSections[id, displayDescription, displayFormName, sortOrder, trackedEntityAttributes]',
`programTrackedEntityAttributes[${programTrackedEntityAttributeFields}]`,
-].join(',');
+];
+
+const pluralProgramFields = [
+ 'displayEnrollmentsLabel',
+ 'displayProgramStagesLabel',
+ 'displayEventsLabel',
+];
+
+const buildFieldsParam = (includePluralLabels: boolean): string => {
+ const stageFields = includePluralLabels
+ ? [...baseProgramStageFields, ...pluralProgramStageFields]
+ : baseProgramStageFields;
+ const programFields = includePluralLabels
+ ? [...baseProgramFields, ...pluralProgramFields]
+ : baseProgramFields;
+ return [
+ ...programFields,
+ `programStages[${stageFields.join(',')}]`,
+ ].join(',');
+};
export const storePrograms = (programIds: Array) => {
+ const includePluralLabels = featureAvailable(FEATURES.customTerminologyPlurals);
const query = {
resource: 'programs',
params: {
- fields: fieldsParam,
+ fields: buildFieldsParam(includePluralLabels),
filter: `id:in:[${programIds.join(',')}]`,
pageSize: programIds.length,
},
diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityTypes/quickStoreOperations/storeTrackedEntityTypes.ts b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityTypes/quickStoreOperations/storeTrackedEntityTypes.ts
index 1e6f04c161..4271797227 100644
--- a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityTypes/quickStoreOperations/storeTrackedEntityTypes.ts
+++ b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityTypes/quickStoreOperations/storeTrackedEntityTypes.ts
@@ -26,7 +26,8 @@ const convert = (() => {
}));
})();
-const fieldsParam = 'id,access,displayName,displayTrackedEntityTypesLabel,minAttributesRequiredToSearch,featureType,' +
+const FIELDS =
+ 'id,access,displayName,minAttributesRequiredToSearch,featureType,' +
'trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],' +
'translations[property,locale,value]';
@@ -34,7 +35,7 @@ export const storeTrackedEntityTypes = (ids: Array) => {
const query = {
resource: 'trackedEntityTypes',
params: {
- fields: fieldsParam,
+ fields: FIELDS,
filter: `id:in:[${ids.join(',')}]`,
pageSize: ids.length,
},
diff --git a/src/core_modules/capture-core/trackedEntityInstances/getDisplayName.ts b/src/core_modules/capture-core/trackedEntityInstances/getDisplayName.ts
index e70af09e5d..577af518e8 100644
--- a/src/core_modules/capture-core/trackedEntityInstances/getDisplayName.ts
+++ b/src/core_modules/capture-core/trackedEntityInstances/getDisplayName.ts
@@ -2,8 +2,6 @@ import i18n from '@dhis2/d2-i18n';
import type { DataElement } from '../metaData';
import { convertClientToView } from '../converters';
-const DEFAULT_NAME = i18n.t('tracked entity instance');
-
export function getDisplayName(
values: { [attrId: string]: any },
attributes: Array,
@@ -13,7 +11,7 @@ export function getDisplayName(
const displayValues = attributes.filter(a => valueIds.some(id => id === a.id) && a.displayInReports);
if (displayValues.length === 0) {
- return fallbackName || DEFAULT_NAME;
+ return fallbackName || i18n.t('tracked entity instance');
}
return displayValues.slice(0, 2)
diff --git a/src/core_modules/capture-core/utils/capitalizeFirstLetter.ts b/src/core_modules/capture-core/utils/capitalizeFirstLetter.ts
new file mode 100644
index 0000000000..cf3370402a
--- /dev/null
+++ b/src/core_modules/capture-core/utils/capitalizeFirstLetter.ts
@@ -0,0 +1,11 @@
+import i18n from '@dhis2/d2-i18n';
+
+export const capitalizeFirstLetter = (str: string): string => {
+ if (!str) return str;
+ const locale = (i18n as any).language ?? 'en';
+ try {
+ return str.charAt(0).toLocaleUpperCase(locale) + str.slice(1);
+ } catch {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+ }
+};