compose-multiplatform
compose-multiplatform copied to clipboard
Provide a way to programmatically change the local `ResourceEnvironment` (`*Qualifier`)
I need to have an ability to programmatically switch between sets of resources (based on their qualifiers). I need this feature to provide my users a way for changing the app language/interface size/theme/etc.
Fortunately, it should be quite easy to implement. The only thing needed is to make LocalComposeEnvironment
public
and maybe add some utility functions (WithLanguage
/WithRegion
/...).
Basically, I want to be able to programmatically and locally change ResourceEnvironment
:
internal data class ResourceEnvironment(
val language: LanguageQualifier,
val region: RegionQualifier,
val theme: ThemeQualifier,
val density: DensityQualifier
)
If it's ok as a feature, I'm more than happy to help!
You can override the density via the CompositionLocalProvider
CompositionLocalProvider(
LocalDensity provides Density(1.5f)
) {
//compose
}
for the other environment values the Compose doesn't provide providers yet. this should be implemented in the Compose and the library will use it
I just wanted to voice my support for this enhancement. This is particularly helpful for allowing users to select their language in an application. I'm not sure if Compose will ever implement support for this because Android has alternative methods of changing the language from inside the application via configurations and/or per-app language preferences. Those existing methods do not work for iOS or Desktop and configuring the application language in-app is more prevalent on Desktop vs Android/iOS.
At the very least it would be helpful to add a function that allows passing in a language qualifier or something similar. That would allow 3rd party libraries to wrap the resources library and work around this limitation.
fun stringResource(resource: StringResource, language: LanguageQualifier)
Very Important for us!
We have an existing ios app. which is created with custom localization handling. That does not fit into composed resources. case so need to feed local from out side
Hi, I tried to use Locale.setDefault(Locale.forLanguageTag("tag"))
and on Desktop
works correctly, obviuosly using the composeResources
api with the specific language tag for values folder (values-en, etc...)
I don't know if this can work on mobile platforms or on web also
This would be great for the web too. Currently, the only way for users to change the language of Compose website is by changing their system's language.
Hi, I'm also looking to be able to change resources programmatically or have multiple instances with different configurations, similar to @ryanmitchener.
I'm developing a fullstack solution in Kotlin and want to use the same i18n solution (StringResource handling) for both client and server.
However, I'm currently blocked because the ResourceEnvironment constructor is internal. Would it be possible to make it public? I can't use Locale.setDefault() as I'm handling multiple requests simultaneously, which would cause a race condition.
There might be some headless issues that shows up here as well...
Currently, the stringResource
function relies on Locale.current
to determine the appropriate string translation. However, Locale.current
always returns the default system locale and doesn't allow for dynamic changes within the app. This makes it impossible to switch languages without changing the system locale.
Here's how the current implementation works:
-
stringResource
callsrememberResourceState
:@Composable fun stringResource(resource: StringResource): String { // ... val str by rememberResourceState(resource, { "" }) { env -> loadString(resource, resourceReader, env) } // ... }
-
rememberResourceState
depends onLocalComposeEnvironment
:@Composable internal actual fun <T> rememberResourceState( // ... ): State<T> { val environment = LocalComposeEnvironment.current.rememberEnvironment() // ... }
-
LocalComposeEnvironment
delegates toDefaultComposeEnvironment
, which usesLocale.current
:internal val DefaultComposeEnvironment = object : ComposeEnvironment { @Composable override fun rememberEnvironment(): ResourceEnvironment { val composeLocale = Locale.current // <-- Problem: Always system locale // ... } }
Proposed Modification:
To allow dynamic locale changes, I propose the following changes to ComposeEnvironment
and DefaultComposeEnvironment
:
--- a/ComposeEnvironment.kt
+++ b/ComposeEnvironment.kt
@@ -1,19 +1,35 @@
internal interface ComposeEnvironment {
@Composable
fun rememberEnvironment(): ResourceEnvironment
+
+ fun setLocale(
+ locale: Locale,
+ )
}
internal val DefaultComposeEnvironment = object : ComposeEnvironment {
+ private lateinit var environment: MutableState<ResourceEnvironment>
+
@Composable
override fun rememberEnvironment(): ResourceEnvironment {
+ if (::environment.isInitialized) {
+ return environment.value
+ }
val composeLocale = Locale.current
val composeTheme = isSystemInDarkTheme()
val composeDensity = LocalDensity.current
- //cache ResourceEnvironment unless compose environment is changed
- return remember(composeLocale, composeTheme, composeDensity) {
- ResourceEnvironment(
+ environment = remember(composeLocale, composeTheme, composeDensity) {
+ mutableStateOf(
+ ResourceEnvironment(
LanguageQualifier(composeLocale.language),
RegionQualifier(composeLocale.region),
ThemeQualifier.selectByValue(composeTheme),
DensityQualifier.selectByDensity(composeDensity.density)
- )
+ )
+ )
}
+ return environment.value
}
+
+ // function to change the environment
+ override fun setLocale(
+ locale: Locale,
+ ) {
+ environment.value = ResourceEnvironment(
+ LanguageQualifier(locale.language),
+ RegionQualifier(locale.region),
+ environment.value.theme,
+ environment.value.density
+ )
+ }
}
+
This modification introduces two key changes:
-
setLocale
function: This function allows directly updating the locale used byrememberEnvironment
, enabling dynamic language switching within the app. -
Mutable
environment
: Instead of recreatingResourceEnvironment
on every recomposition, we now use aMutableState
to hold the environment. This allows us to modify the locale within the environment without triggering unnecessary recomposition of the entire composable tree.
I tested it using the resources demo (Desktop for now) with the following code:
@Composable
fun LocaleController() {
var localeTextField by remember { mutableStateOf("ar") }
val locale = LocalComposeEnvironment.current
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = localeTextField,
onValueChange = { localeTextField = it },
label = { Text("Locale") },
enabled = true,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledContainerColor = MaterialTheme.colorScheme.surface,
disabledLabelColor = MaterialTheme.colorScheme.onSurface,
)
)
OutlinedButton(
onClick = {
locale.setLocale(Locale(localeTextField))
},
modifier = Modifier.padding(16.dp)
) {
Text("Change Locale")
}
}
This code snippet provides a simple UI with a text field to input the desired locale code and a button to trigger the locale change using the new setLocale
function.