Last active
February 6, 2023 09:49
-
-
Save NaingAungLuu/c258288c947f8a348f337cb6002d27b8 to your computer and use it in GitHub Desktop.
Dynamic Localization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum class AppLanguage(val value: String) { | |
ENGLISH("English"), | |
BURMESE("Burmese"), | |
CHINESE("Chinese"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<layout xmlns:android="http://schemas.android.com/apk/res/android"> | |
<data> | |
<variable | |
name="localization" | |
type="me.naingaungluu.dynamiclocalization.data.models.Localization" /> | |
</data> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="@{localization.lblGreeting}" | |
/> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HomeFragment() : BaseFragment() { | |
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
super.onViewCreated(view, savedInstanceState) | |
//Collect Flow | |
viewModel.localizationFlow | |
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) | |
.onEach { | |
binding.localization = it | |
} | |
.launchIn(lifecycleScope) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun HomeScreen( | |
viewModel: HomeViewModel = hiltViewModel() | |
) { | |
HomeScreenStateless( | |
HomeScreenState( | |
data = HomeScreenState.Data( | |
localization = viewModel.localizationFlow.collectAsState( | |
initial = Localization.getDefaultLocalization() | |
), | |
appLanguage = viewModel.currentLanguageFlow.collectAsState( | |
initial = AppLanguage.ENGLISH | |
) | |
), | |
delegates = HomeScreenState.Delegate( | |
onTapEnglish = viewModel::switchToEnglish, | |
onTapBurmese = viewModel::switchToBurmese, | |
onTapChinese = viewModel::switchToChinese | |
) | |
) | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@HiltViewModel | |
class HomeViewModel @Inject constructor() : LocalizedViewModel() { | |
private val _currentLanguage: MutableStateFlow<AppLanguage> = MutableStateFlow(AppLanguage.ENGLISH) | |
val currentLanguageFlow: Flow<AppLanguage> = _currentLanguage | |
fun switchToEnglish() = switchLanguage(AppLanguage.ENGLISH) | |
fun switchToChinese() = switchLanguage(AppLanguage.CHINESE) | |
fun switchToBurmese() = switchLanguage(AppLanguage.BURMESE) | |
private fun switchLanguage(language: AppLanguage) { | |
viewModelScope.launch { | |
localizationRepository.updateLanguage(language) | |
_currentLanguage.emit(language) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"en" : { | |
"lblGreeting" : "Hello", | |
"lblSelectedLanguage" : "Selected Language : %@", | |
"lblEnglish" : "English", | |
"lblBurmese" : "Burmese", | |
"lblChinese" : "Chinese" | |
}, | |
"cn" : { | |
"lblGreeting" : "你好", | |
"lblSelectedLanguage" : "选择的语言 : %@", | |
"lblEnglish" : "英语", | |
"lblBurmese" : "缅甸语", | |
"lblChinese" : "中文" | |
}, | |
"mm" : { | |
"lblGreeting" : "မင်္ဂလာပါ။", | |
"lblSelectedLanguage" : "ရွေးချယ်ထားသောဘာသာစကား : %@", | |
"lblEnglish" : "အင်္ဂလိပ်", | |
"lblBurmese" : "ဗမာ", | |
"lblChinese" : "တရုတ်" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Localization : Serializable { | |
/** | |
* Properties | |
*/ | |
var lblGreeting : String = "" | |
var lblSelectedLanguage : String = "" | |
var lblEnglish : String = "" | |
var lblBurmese : String = "" | |
var lblChinese : String = "" | |
companion object { | |
//dummy | |
private val dummy = Localization().apply { | |
lblGreeting = "Hello" | |
lblSelectedLanguage = "Selected Language : %@" | |
lblEnglish = "English" | |
lblChinese = "Chinese" | |
lblBurmese = "Burmese" | |
} | |
fun getDefaultLocalization() : Localization = dummy | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Localization : Serializable { | |
// ... | |
companion object { | |
const val TEMPLATE_HANDLE = "%@" | |
fun getTemplatedString(format: String, vararg params: String): String | |
= if (params.isNotEmpty() && format.contains(TEMPLATE_HANDLE)) | |
getTemplatedString( | |
format.replaceFirst(TEMPLATE_HANDLE, params.first()), | |
*params.drop(1).toTypedArray() | |
) | |
else | |
format | |
// Localization.getTemplatedString("Value : %@" , "Hello') returns "Value: Hello" | |
} | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun getTemplatedString(format: String, vararg params: String): String | |
= if (params.isNotEmpty() && format.contains("%@")) { | |
getTemplatedString( | |
format.replaceFirst("%@", params.first()), | |
*params.drop(1).toTypedArray() | |
) | |
} else { | |
format | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data class LocalizationBundle( | |
val en : Localization = Localization.getDefaultLocalization(), | |
val cn : Localization = Localization.getDefaultLocalization(), | |
val mm : Localization = Localization.getDefaultLocalization() | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LocalizationLocalImpl @Inject constructor( | |
@ApplicationContext private val context : Context | |
): LocalizationLocal { | |
private val moshiAdapter : JsonAdapter<LocalizationBundle> by lazy { | |
Moshi.Builder() | |
.add(KotlinJsonAdapterFactory()) | |
.build() | |
.adapter(LocalizationBundle::class.java) | |
} | |
override fun getLocalizationBundle(): LocalizationBundle { | |
// Fetch Localization Bundle from Raw Asset | |
val localizationJson = getLocalizationJsonFromLocal() | |
val localizationBundle = moshiAdapter.fromJson(localizationJson) | |
return localizationBundle ?: LocalizationBundle() | |
} | |
private fun getLocalizationJsonFromLocal() : String | |
= context.resources | |
.openRawResource(R.raw.localization) | |
.bufferedReader() | |
.use { it.readText() } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface LocalizationRepository { | |
val localizationFlow: StateFlow<Localization> | |
val currentAppLanguage: AppLanguage | |
suspend fun updateLanguage(language: AppLanguage) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LocalizationRepositoryImpl @Inject constructor( | |
private val preferenceStorage: PreferenceStorage, | |
private val local: LocalizationLocal, | |
private val remote: LocalizationRemote, | |
) : LocalizationRepository { | |
private val _localizationFlow: MutableStateFlow<Localization> by lazy { | |
MutableStateFlow( | |
local.getLocalizationBundle().getLocalization(currentAppLanguage) | |
) | |
} | |
override val localizationFlow: StateFlow<Localization> = _localizationFlow | |
private var cachedLocalizationBundle : LocalizationBundle? = null | |
override val currentAppLanguage: AppLanguage | |
get() = preferenceStorage.getAppLanguage() | |
init { | |
CoroutineScope(Dispatchers.IO).launch { | |
getLocalizationFromRemote() | |
} | |
} | |
override suspend fun updateLanguage(language: AppLanguage) { | |
_localizationFlow.value = getLocalization(language) | |
preferenceStorage.saveAppLanguage(language) | |
} | |
private suspend fun getLocalizationFromRemote() { | |
// Fetch localization data from remote here | |
remote.getLocalizationBundle().let { | |
cachedLocalizationBundle = it | |
_localizationFlow.value = it.getLocalization(currentAppLanguage) | |
} | |
} | |
private fun getLocalization(language: AppLanguage) : Localization | |
= cachedLocalizationBundle?.getLocalization(language) | |
?: local.getLocalizationBundle().getLocalization(language) | |
fun LocalizationBundle.getLocalization(language: AppLanguage): Localization | |
= when(language) { | |
AppLanguage.ENGLISH -> en | |
AppLanguage.CHINESE -> cn | |
AppLanguage.BURMESE -> mm | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
private val _localizationFlow: MutableStateFlow<Localization> by lazy { | |
MutableStateFlow( | |
// ... | |
) | |
} | |
override val localizationFlow: StateFlow<Localization> = _localizationFlow | |
private var cachedLocalizationBundle : LocalizationBundle? = null | |
... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
private fun getLocalization(language: AppLanguage) : Localization | |
= cachedLocalizationBundle?.getLocalization(language) | |
?: local.getLocalizationBundle().getLocalization(language) | |
fun LocalizationBundle.getLocalization(language: AppLanguage): Localization | |
= when(language) { | |
AppLanguage.ENGLISH -> en | |
AppLanguage.CHINESE -> cn | |
AppLanguage.BURMESE -> mm | |
} | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class LocalizedViewModel : ViewModel() { | |
@Inject | |
protected lateinit var localizationRepository: LocalizationRepository | |
val localizationFlow : StateFlow<Localization> | |
get() = localizationRepository.localizationFlow | |
// To get Localization without having to collect flow | |
val localization : Localization | |
get() = localizationFlow.value | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment