Abrarul8541 commited on
Commit
528d09a
·
verified ·
1 Parent(s): 9b82783

i18n: remember chosen language across launches, timeouts, error on bad HTTP

Browse files
app/src/main/java/io/github/abrarul/modularai/i18n/LocaleManager.kt CHANGED
@@ -8,6 +8,7 @@ import kotlinx.coroutines.Dispatchers
8
  import kotlinx.coroutines.withContext
9
  import org.json.JSONObject
10
  import java.io.File
 
11
  import java.net.URL
12
 
13
  /**
@@ -16,24 +17,41 @@ import java.net.URL
16
  */
17
  class LocaleManager(ctx: Context, private val baseUrl: String) {
18
  private val dir = File(ctx.filesDir, "locales").apply { mkdirs() }
 
19
  private var strings by mutableStateOf<Map<String, String>>(emptyMap())
20
  var current by mutableStateOf("en")
21
  private set
22
 
23
- /** English fallback keys live in code; missing keys return the key's default. */
24
  fun t(key: String, default: String): String = strings[key] ?: default
25
 
26
  suspend fun apply(code: String, persist: Boolean = true) = withContext(Dispatchers.IO) {
 
27
  val cached = File(dir, "$code.json")
28
- val text = if (cached.exists()) cached.readText() else URL("$baseUrl/$code.json").readText()
 
 
 
 
 
 
29
  val json = JSONObject(text)
30
  val map = json.keys().asSequence().associateWith { json.getString(it) }
31
- if (persist) cached.writeText(text)
 
 
 
32
  strings = map
33
  current = code
34
  }
35
 
36
- fun reset() { strings = emptyMap(); current = "en" }
37
- fun purge(code: String) { File(dir, "$code.json").delete() }
38
- fun purgeAll() { dir.listFiles()?.forEach { it.delete() } }
 
 
 
 
 
 
39
  }
 
8
  import kotlinx.coroutines.withContext
9
  import org.json.JSONObject
10
  import java.io.File
11
+ import java.net.HttpURLConnection
12
  import java.net.URL
13
 
14
  /**
 
17
  */
18
  class LocaleManager(ctx: Context, private val baseUrl: String) {
19
  private val dir = File(ctx.filesDir, "locales").apply { mkdirs() }
20
+ private val prefs = ctx.getSharedPreferences("locale", Context.MODE_PRIVATE)
21
  private var strings by mutableStateOf<Map<String, String>>(emptyMap())
22
  var current by mutableStateOf("en")
23
  private set
24
 
25
+ /** English defaults live in code; a missing key returns [default]. */
26
  fun t(key: String, default: String): String = strings[key] ?: default
27
 
28
  suspend fun apply(code: String, persist: Boolean = true) = withContext(Dispatchers.IO) {
29
+ require(code.matches(Regex("[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})?"))) { "Invalid language code: $code" }
30
  val cached = File(dir, "$code.json")
31
+ val text = if (cached.exists()) cached.readText() else {
32
+ val c = URL("$baseUrl/$code.json").openConnection() as HttpURLConnection
33
+ c.connectTimeout = 15_000
34
+ c.readTimeout = 15_000
35
+ if (c.responseCode !in 200..299) error("Language '$code' not available (HTTP ${c.responseCode})")
36
+ c.inputStream.bufferedReader().use { it.readText() }
37
+ }
38
  val json = JSONObject(text)
39
  val map = json.keys().asSequence().associateWith { json.getString(it) }
40
+ if (persist) {
41
+ cached.writeText(text)
42
+ prefs.edit().putString("code", code).apply()
43
+ }
44
  strings = map
45
  current = code
46
  }
47
 
48
+ /** Re-applies the saved language from cache at app start; no network needed. */
49
+ suspend fun restore() {
50
+ val code = prefs.getString("code", null) ?: return
51
+ if (File(dir, "$code.json").exists()) apply(code, persist = false)
52
+ }
53
+
54
+ fun reset() { strings = emptyMap(); current = "en"; prefs.edit().remove("code").apply() }
55
+ fun purge(code: String) { File(dir, "$code.json").delete(); if (current == code) reset() }
56
+ fun purgeAll() { dir.listFiles()?.forEach { it.delete() }; reset() }
57
  }