mirror of
https://github.com/processing/processing4.git
synced 2026-01-29 11:21:06 +01:00
Refactor Locale class and add LocaleProvider test (#1283)
* Refactor Locale class and add LocaleProvider test * Make setLocale parameter nullable in Locale class Changed the setLocale parameter in the Locale class to be nullable and updated its usage to safely invoke it. This allows for more flexible instantiation when a setLocale function is not required. * Add compose ui test to the deps * Update locale change method in test Replaces the call to locale.setLocale with locale.set in LocaleKtTest to match the updated API for changing the locale.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import org.gradle.internal.jvm.Jvm
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
||||
import org.jetbrains.compose.ExperimentalComposeLibrary
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask
|
||||
import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download
|
||||
@@ -119,6 +120,8 @@ dependencies {
|
||||
implementation(libs.markdown)
|
||||
implementation(libs.markdownJVM)
|
||||
|
||||
@OptIn(ExperimentalComposeLibrary::class)
|
||||
testImplementation(compose.uiTest)
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(libs.mockitoKotlin)
|
||||
testImplementation(libs.junitJupiter)
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
package processing.app.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import processing.app.LocalPreferences
|
||||
import processing.app.Messages
|
||||
import processing.app.Platform
|
||||
import processing.app.PlatformStart
|
||||
import processing.app.watchFile
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import processing.app.*
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
class Locale(language: String = "") : Properties() {
|
||||
/**
|
||||
* The Locale class extends the standard Java Properties class
|
||||
* to provide localization capabilities.
|
||||
* It loads localization resources from property files based on the specified language code.
|
||||
* The class also provides a method to change the current locale and update the application accordingly.
|
||||
* Usage:
|
||||
* ```
|
||||
* val locale = Locale("es") { newLocale ->
|
||||
* // Handle locale change, e.g., update UI or restart application
|
||||
* }
|
||||
* val localizedString = locale["someKey"]
|
||||
* ```
|
||||
*/
|
||||
class Locale(language: String = "", val setLocale: ((java.util.Locale) -> Unit)? = null) : Properties() {
|
||||
var locale: java.util.Locale = java.util.Locale.getDefault()
|
||||
|
||||
init {
|
||||
val locale = java.util.Locale.getDefault()
|
||||
load(ClassLoader.getSystemResourceAsStream("PDE.properties"))
|
||||
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream())
|
||||
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream())
|
||||
load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream())
|
||||
loadResourceUTF8("PDE.properties")
|
||||
loadResourceUTF8("PDE_${locale.language}.properties")
|
||||
loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties")
|
||||
loadResourceUTF8("PDE_${language}.properties")
|
||||
}
|
||||
|
||||
fun loadResourceUTF8(path: String) {
|
||||
val stream = ClassLoader.getSystemResourceAsStream(path)
|
||||
stream?.reader(charset = Charsets.UTF_8)?.use { reader ->
|
||||
load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use get instead", ReplaceWith("get(key)"))
|
||||
@@ -28,18 +45,86 @@ class Locale(language: String = "") : Properties() {
|
||||
return value
|
||||
}
|
||||
operator fun get(key: String): String = getProperty(key, key)
|
||||
fun set(locale: java.util.Locale) {
|
||||
setLocale?.invoke(locale)
|
||||
}
|
||||
}
|
||||
val LocalLocale = compositionLocalOf { Locale() }
|
||||
/**
|
||||
* A CompositionLocal to provide access to the Locale instance
|
||||
* throughout the composable hierarchy. see [LocaleProvider]
|
||||
* Usage:
|
||||
* ```
|
||||
* val locale = LocalLocale.current
|
||||
* val localizedString = locale["someKey"]
|
||||
* ```
|
||||
*/
|
||||
val LocalLocale = compositionLocalOf<Locale> { error("No Locale Set") }
|
||||
|
||||
/**
|
||||
* This composable function sets up a locale provider that manages application localization.
|
||||
* It initializes the locale from a language file, watches for changes to that file, and updates
|
||||
* the locale accordingly. It uses a [Locale] class to handle loading of localized resources.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* LocaleProvider {
|
||||
* // Your app content here
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* To access the locale:
|
||||
* ```
|
||||
* val locale = LocalLocale.current
|
||||
* val localizedString = locale["someKey"]
|
||||
* ```
|
||||
*
|
||||
* To change the locale:
|
||||
* ```
|
||||
* locale.set(java.util.Locale("es"))
|
||||
* ```
|
||||
* This will update the `language.txt` file and reload the locale.
|
||||
*/
|
||||
@Composable
|
||||
fun LocaleProvider(content: @Composable () -> Unit) {
|
||||
PlatformStart()
|
||||
val preferencesFolderOverride: File? = System.getProperty("processing.app.preferences.folder")?.let { File(it) }
|
||||
|
||||
val settingsFolder = Platform.getSettingsFolder()
|
||||
val languageFile = File(settingsFolder, "language.txt")
|
||||
watchFile(languageFile)
|
||||
val settingsFolder = preferencesFolderOverride ?: remember{
|
||||
Platform.init()
|
||||
Platform.getSettingsFolder()
|
||||
}
|
||||
val languageFile = settingsFolder.resolve("language.txt")
|
||||
remember(languageFile){
|
||||
if(languageFile.exists()) return@remember
|
||||
|
||||
val locale = Locale(languageFile.readText().substring(0, 2))
|
||||
CompositionLocalProvider(LocalLocale provides locale) {
|
||||
content()
|
||||
Messages.log("Creating language file at ${languageFile.absolutePath}")
|
||||
settingsFolder.mkdirs()
|
||||
languageFile.writeText(java.util.Locale.getDefault().language)
|
||||
}
|
||||
|
||||
val update = watchFile(languageFile)
|
||||
var code by remember(languageFile, update){ mutableStateOf(languageFile.readText().substring(0, 2)) }
|
||||
remember(code) {
|
||||
val locale = java.util.Locale(code)
|
||||
java.util.Locale.setDefault(locale)
|
||||
}
|
||||
|
||||
fun setLocale(locale: java.util.Locale) {
|
||||
Messages.log("Setting locale to ${locale.language}")
|
||||
languageFile.writeText(locale.language)
|
||||
code = locale.language
|
||||
}
|
||||
|
||||
|
||||
val locale = Locale(code, ::setLocale)
|
||||
remember(code) { Messages.log("Loaded Locale: $code") }
|
||||
val dir = when(locale["locale.direction"]) {
|
||||
"rtl" -> LayoutDirection.Rtl
|
||||
else -> LayoutDirection.Ltr
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides dir) {
|
||||
CompositionLocalProvider(LocalLocale provides locale) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
52
app/test/processing/app/LocaleKtTest.kt
Normal file
52
app/test/processing/app/LocaleKtTest.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
package processing.app
|
||||
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.runComposeUiTest
|
||||
import processing.app.ui.theme.LocalLocale
|
||||
import processing.app.ui.theme.LocaleProvider
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.test.Test
|
||||
|
||||
class LocaleKtTest {
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
@Test
|
||||
fun testLocale() = runComposeUiTest {
|
||||
val tempPreferencesDir = createTempDirectory("preferences")
|
||||
|
||||
System.setProperty("processing.app.preferences.folder", tempPreferencesDir.toFile().absolutePath)
|
||||
|
||||
setContent {
|
||||
LocaleProvider {
|
||||
val locale = LocalLocale.current
|
||||
Text(locale["menu.file.new"], modifier = Modifier.testTag("localisedText"))
|
||||
|
||||
Button(onClick = {
|
||||
locale.set(java.util.Locale("es"))
|
||||
}, modifier = Modifier.testTag("button")) {
|
||||
Text("Change")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if usage generates the language file if it doesn't exist
|
||||
val languageFile = tempPreferencesDir.resolve("language.txt").toFile()
|
||||
assert(languageFile.exists())
|
||||
|
||||
// Check if the text is localised
|
||||
onNodeWithTag("localisedText").assertTextEquals("New")
|
||||
|
||||
// Change the locale to Spanish
|
||||
onNodeWithTag("button").performClick()
|
||||
onNodeWithTag("localisedText").assertTextEquals("Nuevo")
|
||||
|
||||
// Check if the preference was saved to file
|
||||
assert(languageFile.readText().substring(0, 2) == "es")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user