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 @@ -46,8 +46,8 @@ abstract class BaseMultiLanguageActivity : AppCompatActivity() {
val lang =
baseContext
.getSharedPreferences(SharedPreferencesHelper.PREFS_NAME, Context.MODE_PRIVATE)
.getString(SharedPreferenceKey.LANG.name, Locale.ENGLISH.toLanguageTag())
?: Locale.ENGLISH.toLanguageTag()
.getString(SharedPreferenceKey.LANG.name, Locale.getDefault().toLanguageTag())
?: Locale.getDefault().toLanguageTag()
baseContext.setAppLocale(lang).run {
super.attachBaseContext(baseContext)
applyOverrideConfiguration(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2021-2024 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.util

import java.util.Locale
import org.hl7.fhir.r4.model.Basic
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Coding
import timber.log.Timber

/**
* Utility class for creating and managing a FHIR Basic resource that represents the current device
* language. This resource is used as a launch context in questionnaires, allowing variable
* definitions to conditionally display text based on the device's language setting.
*
* The Basic resource contains language information using ISO 639-1 language codes (e.g., en, es,
* fr).
*/
object LanguageBasicUtil {
/**
* Fixed identifier for the device language resource. This ensures the resource can be
* consistently referenced and updated.
*/
const val LANGUAGE_BASIC_ID = "device-language"

/**
* Creates a Basic FHIR resource representing the current device language.
*
* @return A Basic FHIR resource configured with the current device language information.
*
* Example usage:
* ```kotlin
* val languageBasic = LanguageBasicUtil.createLanguageBasic()
* // languageBasic can then be used as a launch context in questionnaires
* ```
*/
fun createLanguageBasic(): Basic {
val languageCode = getCurrentLanguageCode()
val languageDisplay = getCurrentLanguageDisplay()

return Basic().apply {
id = LANGUAGE_BASIC_ID

code =
CodeableConcept()
.addCoding(
Coding(
"urn:ietf:bcp:47",
languageCode,
languageDisplay,
),
)

Timber.d("Created language basic resource with language code: $languageCode")
}
}

/**
* Retrieves the current device language code in ISO 639-1 format (e.g., en, es, fr).
*
* @return The ISO 639-1 language code of the device's current locale.
*/
private fun getCurrentLanguageCode(): String {
return Locale.getDefault().language
}

/**
* Retrieves the display name for the current device language.
*
* @return The display name of the device's language (e.g., "English", "Spanish", "French").
*/
private fun getCurrentLanguageDisplay(): String {
return Locale.getDefault().displayLanguage
}

/**
* Checks if a language code matches the current device language.
*
* This is useful for FHIRPath expressions that need to conditionally evaluate based on language.
*
* @param languageCode The ISO 639-1 language code to check (e.g., "en", "es", "fr").
* @return True if the device language matches the provided code, false otherwise.
*/
fun isLanguage(languageCode: String): Boolean {
return getCurrentLanguageCode() == languageCode
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021-2024 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.util

import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.smartregister.fhircore.engine.robolectric.RobolectricTest

/**
* Unit tests for [LanguageBasicUtil].
*
* Tests the creation and management of the Basic FHIR resource representing the device language,
* and the ability to check language codes.
*/
@RunWith(RobolectricTestRunner::class)
class LanguageBasicUtilTest : RobolectricTest() {

@Test
fun testCreateLanguageBasicReturnsValidBasic() {
val basic = LanguageBasicUtil.createLanguageBasic()

// Verify the basic resource is created with the correct ID
Assert.assertEquals(LanguageBasicUtil.LANGUAGE_BASIC_ID, basic.id)

// Verify the basic resource has a code
Assert.assertNotNull(basic.code)
Assert.assertFalse(basic.code.coding.isEmpty())
}

@Test
fun testCreateLanguageBasicHasLanguageCode() {
val basic = LanguageBasicUtil.createLanguageBasic()

// Verify the basic resource has a code field with coding
Assert.assertNotNull(basic.code)
Assert.assertFalse(basic.code.coding.isEmpty())

// Verify the coding has the correct system
val languageCoding = basic.code.coding.first()
Assert.assertEquals("urn:ietf:bcp:47", languageCoding.system)

// Verify the language code is in ISO 639-1 format (2-letter code)
Assert.assertNotNull(languageCoding.code)
Assert.assertTrue(languageCoding.code.matches(Regex("[a-z]{2}")))

// Verify the language display is not empty
Assert.assertNotNull(languageCoding.display)
Assert.assertFalse(languageCoding.display.isEmpty())
}

@Test
fun testDeviceLanguageIdConstant() {
// Verify the constant ID is set correctly
Assert.assertEquals("device-language", LanguageBasicUtil.LANGUAGE_BASIC_ID)
}

@Test
fun testIsLanguageWithExactMatch() {
// Test with the current device language
val currentLanguage = java.util.Locale.getDefault().language
Assert.assertTrue(LanguageBasicUtil.isLanguage(currentLanguage))
}

@Test
fun testIsLanguageWithNonMatchingLanguage() {
// Test with a language that's not the current device language
// Using a different language code that's unlikely to be the device language
val nonMatchingLanguage = if (java.util.Locale.getDefault().language != "ja") "ja" else "es"
Assert.assertFalse(LanguageBasicUtil.isLanguage(nonMatchingLanguage))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import org.smartregister.fhircore.engine.ui.base.AlertDialogue
import org.smartregister.fhircore.engine.ui.base.AlertIntent
import org.smartregister.fhircore.engine.ui.base.BaseMultiLanguageActivity
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.LanguageBasicUtil
import org.smartregister.fhircore.engine.util.extension.encodeResourceToString
import org.smartregister.fhircore.engine.util.extension.parcelable
import org.smartregister.fhircore.engine.util.extension.parcelableArrayList
Expand Down Expand Up @@ -455,8 +456,10 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
?: showToast(getString(R.string.error_populating_questionnaire))
}

val languageBasic = LanguageBasicUtil.createLanguageBasic()
launchContextResources
.associate { Pair(it.resourceType.name.lowercase(), it.json()) }
.plus(LANGUAGE_VARIABLE_NAME to languageBasic.json())
.takeIf { it.isNotEmpty() }
?.let { setQuestionnaireLaunchContextMap(it) }
}
Expand Down Expand Up @@ -615,6 +618,7 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
const val QUESTIONNAIRE_SUBMISSION_EXTRACTED_RESOURCE_IDS = "questionnaireExtractedResourceIds"
const val QUESTIONNAIRE_RESPONSE = "questionnaireResponse"
const val QUESTIONNAIRE_ACTION_PARAMETERS = "questionnaireActionParameters"
const val LANGUAGE_VARIABLE_NAME = "language"

fun intentBundle(
questionnaireConfig: QuestionnaireConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import org.smartregister.fhircore.engine.domain.model.isSummary
import org.smartregister.fhircore.engine.rulesengine.RulesExecutor
import org.smartregister.fhircore.engine.task.FhirCarePlanGenerator
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.LanguageBasicUtil
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.util.extension.allItems
Expand Down Expand Up @@ -1162,8 +1163,10 @@ constructor(

val launchContextResources =
launchContextResources(resourceType, resourceIdentifier, actionParameters)
val launchContexts = launchContextResources.associateBy { it.resourceType.name.lowercase() }

val languageBasic = LanguageBasicUtil.createLanguageBasic()
val launchContexts =
launchContextResources.associateBy { it.resourceType.name.lowercase() }.toMutableMap()
launchContexts["language"] = languageBasic
// Populate questionnaire with initial default values
ResourceMapper.populate(
questionnaire,
Expand Down
Loading