Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,20 @@ constructor(

/**
* This function creates a property key from the string [value] and uses the key to retrieve the
* correct translation from the string.properties file.
* correct translation from the string.properties file. If no translation is found, it falls
* back to the original value.
*/
fun translate(value: String): String =
configurationRegistry.localizationHelper.parseTemplate(
LocalizationHelper.STRINGS_BASE_BUNDLE_NAME,
Locale.getDefault(),
"{{${value.translationPropertyKey()}}}",
)
fun translate(value: String): String {
val translationKey = value.translationPropertyKey()
val template = "{{$translationKey}}"
val translatedText =
configurationRegistry.localizationHelper.parseTemplate(
LocalizationHelper.STRINGS_BASE_BUNDLE_NAME,
Locale.getDefault(),
template,
)
return if (translatedText == template) value else translatedText
}

/**
* This method retrieves a list of relatedResources for a given resource from the facts map It
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,24 @@ fun String.messageFormat(locale: Locale?, vararg arguments: Any?): String? =
MessageFormat(this, locale).format(arguments)

/**
* Creates identifier from string text by doing clean up on the passed value
* Creates a translation property key from string text by normalizing the input.
*
* @return string.properties key to be used in string look ups
* This function:
* 1. Trims leading/trailing whitespace
* 2. Converts to lowercase
* 3. Replaces all non-alphanumeric characters with dots
* 4. Removes leading/trailing dots
*
* Example:
* ```
* "Discuss Confidentiality".translationPropertyKey() // Returns "discuss.confidentiality"
* " C-SSRS ".translationPropertyKey() // Returns "c.ssrs"
* ```
*
* @return string.properties key to be used in translation lookups
*/
fun String.translationPropertyKey(): String {
return this.trim { it <= ' ' }.lowercase(Locale.ENGLISH).replace(" ".toRegex(), ".")
return this.trim().lowercase(Locale.ENGLISH).replace("[^a-z0-9]+".toRegex(), ".").trim('.')
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

package org.smartregister.fhircore.engine.util.extension

import java.util.Locale
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Task
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.util.helper.LocalizationHelper

fun Task.hasPastEnd() =
this.hasExecutionPeriod() &&
Expand Down Expand Up @@ -62,3 +65,29 @@ fun Task.isOverDue() =
this.executionPeriod.end.before(today())

fun Task.isDue() = this.hasStatus() && this.status == Task.TaskStatus.READY

/**
* Retrieves the localized description of the Task using the translation configuration.
*
* This function:
* 1. Takes the task description (typically from ActivityDefinition.productCodeableConcept.text)
* 2. Converts it to a translation property key (e.g., "Discuss Confidentiality" →
* "discuss.confidentiality")
* 3. Looks up the translation in the configured translation bundles for the current locale
* 4. Falls back to the original description if no translation is found
*
* @param configurationRegistry The ConfigurationRegistry instance to access the localization helper
* @return The localized task description, or the original description if no translation found
*/
fun Task.getLocalizedDescription(configurationRegistry: ConfigurationRegistry): String {
val description = this.description ?: return ""
val translationKey = description.translationPropertyKey()
val template = "{{$translationKey}}"
val translatedText =
configurationRegistry.localizationHelper.parseTemplate(
LocalizationHelper.STRINGS_BASE_BUNDLE_NAME,
Locale.getDefault(),
template,
)
return if (translatedText == template) description else translatedText
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,74 @@ class RulesEngineServiceTest : RobolectricTest() {
Assert.assertEquals("Statut Vaccinal Traduit", result)
}

@Test
fun testTranslate_WithValidTranslation_ReturnsTranslatedText() {
// Uses real translation from strings.properties
val result = rulesEngineService.translate("Discuss Confidentiality")

Assert.assertEquals("Discuss Confidentiality (English)", result)
}

@Test
fun testTranslate_WithUnknownText_FallsBackToOriginal() {
val originalText = "Some Unknown Task That Does Not Exist"

val result = rulesEngineService.translate(originalText)

// Should fallback to original text and never return template strings
Assert.assertEquals(originalText, result)
Assert.assertFalse(result.contains("{{"))
Assert.assertFalse(result.contains("}}"))
}

@Test
fun testTranslate_WithEmptyString_ReturnsEmptyString() {
val result = rulesEngineService.translate("")

Assert.assertEquals("", result)
}

@Test
fun testTranslate_WithSpecialCharactersAndNumbers_HandlesCorrectly() {
// Tests dashes, numbers, and special characters
val result = rulesEngineService.translate("PHQ-9")

Assert.assertEquals("PHQ-9 Screening", result)
}

@Test
fun testTranslate_WithWhitespace_TrimsAndTranslates() {
// Tests whitespace trimming and consecutive spaces
val result = rulesEngineService.translate(" IPC Session 1 ")

Assert.assertEquals("IPC Session 1 (English)", result)
}

@Test
fun testTranslate_WithUppercaseInput_MatchesLowercaseKey() {
// Tests case insensitivity - uppercase converted to lowercase for key lookup
val result = rulesEngineService.translate("DISCUSS CONFIDENTIALITY")

Assert.assertEquals("Discuss Confidentiality (English)", result)
}

@Test
fun testTranslate_WithParenthesesInOriginal_HandlesCorrectly() {
// Tests parentheses and dashes in original text
val result = rulesEngineService.translate("Information Only (Low Risk)")

Assert.assertEquals("Information Only - Low Risk Category", result)
}

@Test
fun testTranslate_WithFrenchLocale_ReturnsCorrectTranslation() {
Locale.setDefault(Locale.FRENCH)

val result = rulesEngineService.translate("Discuss Confidentiality")

Assert.assertEquals("Discuter de la Confidentialité", result)
}

@Test
fun testComputeTotalCountShouldReturnSumOfAllCounts() {
val totalCount =
Expand Down
Loading