diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/pdf/HtmlPopulator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/pdf/HtmlPopulator.kt index 7b49518eeaf..bd61d98dad2 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/pdf/HtmlPopulator.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/pdf/HtmlPopulator.kt @@ -31,14 +31,18 @@ import org.smartregister.fhircore.engine.util.extension.valueToString /** * HtmlPopulator class is responsible for processing an HTML template by replacing custom tags with * data from QuestionnaireResponses. The class uses various regex patterns to find and replace - * custom tags such as @is-not-empty, @answer-as-list, @answer, @submitted-date, @contains, - * and @is-questionnaire-submitted. + * custom tags such + * as @is-not-empty, @answer-as-list, @answer, @submitted-date, @contains, @is-questionnaire-submitted, + * and @translate. * * @property questionnaireResponses The QuestionnaireResponses object containing data for * replacement. + * @property translationProvider A high-order function that takes a translation key and returns the + * translated string. Defaults to returning an empty string. */ class HtmlPopulator( questionnaireResponses: List, + private val translationProvider: (String) -> String = { "" }, ) { private var answerMap: Map> private var submittedDateMap: Map @@ -104,6 +108,10 @@ class HtmlPopulator( val matcher = isQuestionnaireSubmittedPattern.matcher(html.substring(i)) if (matcher.find()) processIsQuestionnaireSubmitted(i, html, matcher) else i++ } + html.startsWith("@translate", i) -> { + val matcher = translatePattern.matcher(html.substring(i)) + if (matcher.find()) processTranslate(i, html, matcher) else i++ + } else -> i++ } } @@ -246,6 +254,20 @@ class HtmlPopulator( } } + /** + * Processes the @translate tag by calling the translation provider lambda to resolve the + * translation key. + * + * @param i The starting index of the tag in the HTML. + * @param html The StringBuilder containing the HTML. + * @param matcher The Matcher object for the regex pattern. + */ + private fun processTranslate(i: Int, html: StringBuilder, matcher: Matcher) { + val translationKey = matcher.group(1) + val translatedValue = translationProvider(translationKey) + html.replace(i, matcher.end() + i, translatedValue) + } + companion object { // Compile regex patterns for different tags private val isNotEmptyPattern = @@ -260,5 +282,6 @@ class HtmlPopulator( Pattern.compile( "@is-questionnaire-submitted\\('([^']+)'\\)((?s).*?)@is-questionnaire-submitted\\('\\1'\\)", ) + private val translatePattern = Pattern.compile("@translate\\('([^']+)'\\)") } } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/pdf/HtmlPopulatorTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/pdf/HtmlPopulatorTest.kt index df51ac3af6f..509f9b20b3c 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/pdf/HtmlPopulatorTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/pdf/HtmlPopulatorTest.kt @@ -665,4 +665,57 @@ class HtmlPopulatorTest { val populatedHtml = htmlPopulator.populateHtml(html) Assert.assertEquals("", populatedHtml) } + + @Test + fun testTranslateShouldRemoveTagWhenUsingDefaultProvider() { + val html = "

@translate('hello.world')

" + val questionnaireResponses = listOf() + // HtmlPopulator with default translation provider (returns empty string) + val htmlPopulator = HtmlPopulator(questionnaireResponses) + val populatedHtml = htmlPopulator.populateHtml(html) + // With default provider, the tag should be removed (empty string) + Assert.assertEquals("

", populatedHtml) + } + + @Test + fun testTranslateShouldTranslateUsingProvidedMap() { + val html = "

@translate('hello.world')

" + val questionnaireResponses = listOf() + val translationMap = mapOf("hello.world" to "Hello World") + val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" } + val populatedHtml = htmlPopulator.populateHtml(html) + Assert.assertEquals("

Hello World

", populatedHtml) + } + + @Test + fun testTranslateShouldHandleMultipleTranslateTagsWithMap() { + val html = "

@translate('key1') and @translate('key2')

" + val questionnaireResponses = listOf() + val translationMap = + mapOf( + "key1" to "Value 1", + "key2" to "Value 2", + ) + val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" } + val populatedHtml = htmlPopulator.populateHtml(html) + Assert.assertEquals("

Value 1 and Value 2

", populatedHtml) + } + + @Test + fun testTranslateShouldHandleTranslateTagInComplexContent() { + val html = + "
Welcome: @translate('welcome.message')

Thank you: @translate('thank.you.message')

" + val questionnaireResponses = listOf() + val translationMap = + mapOf( + "welcome.message" to "Welcome to our service", + "thank.you.message" to "Thank you for using our app", + ) + val htmlPopulator = HtmlPopulator(questionnaireResponses) { key -> translationMap[key] ?: "" } + val populatedHtml = htmlPopulator.populateHtml(html) + Assert.assertEquals( + "
Welcome: Welcome to our service

Thank you: Thank you for using our app

", + populatedHtml, + ) + } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherFragment.kt index 6d9d0376b59..16dc6e19a80 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherFragment.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherFragment.kt @@ -108,7 +108,9 @@ class PdfLauncherFragment : DialogFragment() { } val htmlContent = htmlBinary.content.decodeToString() - val populatedHtml = HtmlPopulator(questionnaireResponses).populateHtml(htmlContent) + val populatedHtml = + HtmlPopulator(questionnaireResponses, pdfLauncherViewModel.getTranslationProvider()) + .populateHtml(htmlContent) withContext(Dispatchers.Main) { pdfGenerator.generatePdfWithHtml(populatedHtml, htmlTitle) { dismiss() } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherViewModel.kt index 5c922807b6a..674d03994d3 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/pdf/PdfLauncherViewModel.kt @@ -19,11 +19,14 @@ package org.smartregister.fhircore.quest.ui.pdf import androidx.lifecycle.ViewModel import com.google.android.fhir.search.Search import dagger.hilt.android.lifecycle.HiltViewModel +import java.util.Locale import javax.inject.Inject import org.hl7.fhir.r4.model.Binary import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.ResourceType +import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.data.local.DefaultRepository +import org.smartregister.fhircore.engine.util.helper.LocalizationHelper /** * ViewModel for managing PDF generation related operations. @@ -32,12 +35,14 @@ import org.smartregister.fhircore.engine.data.local.DefaultRepository * required for generating PDFs. * * @param defaultRepository The repository for accessing local data. + * @param configurationRegistry The registry for configuration and localization support. */ @HiltViewModel class PdfLauncherViewModel @Inject constructor( val defaultRepository: DefaultRepository, + val configurationRegistry: ConfigurationRegistry, ) : ViewModel() { /** @@ -86,4 +91,17 @@ constructor( suspend fun retrieveBinary(binaryId: String): Binary? { return defaultRepository.loadResource(binaryId) } + + /** + * Provides a translation lambda for HtmlPopulator. + * + * @return A function that takes a translation key and returns the translated string. + */ + fun getTranslationProvider(): (String) -> String = { translationKey -> + configurationRegistry.localizationHelper.parseTemplate( + LocalizationHelper.STRINGS_BASE_BUNDLE_NAME, + Locale.getDefault(), + "{{$translationKey}}", + ) + } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/pdf/PdfLauncherViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/pdf/PdfLauncherViewModelTest.kt index 7ff831355f3..4646fc94d17 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/pdf/PdfLauncherViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/pdf/PdfLauncherViewModelTest.kt @@ -33,6 +33,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.data.local.ContentCache import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.util.extension.asReference @@ -49,18 +50,20 @@ class PdfLauncherViewModelTest : RobolectricTest() { private lateinit var fhirEngine: FhirEngine private lateinit var defaultRepository: DefaultRepository + private lateinit var configurationRegistry: ConfigurationRegistry private lateinit var viewModel: PdfLauncherViewModel @Before fun setUp() { hiltAndroidRule.inject() fhirEngine = mockk() + configurationRegistry = mockk() defaultRepository = DefaultRepository( fhirEngine = fhirEngine, dispatcherProvider = mockk(), sharedPreferencesHelper = mockk(), - configurationRegistry = mockk(), + configurationRegistry = configurationRegistry, configService = mockk(), configRulesExecutor = mockk(), fhirPathDataExtractor = mockk(), @@ -68,7 +71,7 @@ class PdfLauncherViewModelTest : RobolectricTest() { context = mockk(), contentCache = contentCache, ) - viewModel = PdfLauncherViewModel(defaultRepository) + viewModel = PdfLauncherViewModel(defaultRepository, configurationRegistry) } @Test