Skip to content

Commit 59b949c

Browse files
authored
Merge pull request #11 from tolgee/jirikuchynka/locale-fallbacks-plus-preload-plus-android-layout-inflation
feat: regional locale handling and android improvements
2 parents aea7682 + 59235ae commit 59b949c

File tree

23 files changed

+1172
-92
lines changed

23 files changed

+1172
-92
lines changed

compose/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ fun LocaleSwitcher() {
161161
}.collectAsState(initial = tolgee.getLocale())
162162

163163
Row {
164-
Text(text = stringResource(tolgee, R.string.selected_locale, currentLocale.language))
164+
Text(text = stringResource(tolgee, R.string.selected_locale, currentLocale.toTag("-")))
165165
Button(onClick = { tolgee.setLocale("en") }) {
166166
Text("English")
167167
}
@@ -188,7 +188,7 @@ fun LocaleAwareComponent() {
188188
}.collectAsState(initial = tolgee.getLocale())
189189

190190
// Your UI that depends on the current locale
191-
Text(text = currentLocale.language)
191+
Text(text = currentLocale.toTag("-"))
192192
}
193193
```
194194

core/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,27 @@ Tolgee.init {
161161
}
162162
```
163163

164+
#### Default Language Fallback
165+
166+
Configure a default language to use when the user's locale is not available:
167+
168+
```kotlin
169+
Tolgee.init {
170+
contentDelivery {
171+
url = "https://cdn.tolg.ee/your-cdn-url-prefix"
172+
}
173+
defaultLanguage("en") // Fallback to English when user's locale is unavailable
174+
}
175+
```
176+
177+
When a user's locale (e.g., "zh-Hans-CN") is not available in your project:
178+
1. Tolgee first tries to find the exact locale match
179+
2. If not found, tries intermediate variations (e.g., "zh-Hans-CN" → "zh-Hans")
180+
3. Then tries the base language (e.g., "zh-Hans" → "zh")
181+
4. If still not found, uses the default language (e.g., "en")
182+
183+
This progressive fallback follows BCP 47 locale tag structure.
184+
164185
#### Preloading Translations
165186

166187
```kotlin

core/api/android/core.api

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public class io/tolgee/Tolgee {
1313
public static final fun new (Lio/tolgee/Tolgee$Config;)Lio/tolgee/TolgeeAndroid;
1414
public static final fun new (Lkotlin/jvm/functions/Function1;)Lio/tolgee/TolgeeAndroid;
1515
public fun preload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
16+
public fun preloadAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1617
public final fun removeChangeListener (Lio/tolgee/Tolgee$ChangeListener;)Z
18+
protected final fun resolveLocale (Ljava/util/Locale;)Ljava/util/Locale;
1719
public fun setLocale (Ljava/lang/String;)Ljava/util/Locale;
1820
public fun setLocale (Ljava/util/Locale;)Ljava/util/Locale;
1921
public final fun t (Ljava/lang/String;)Ljava/lang/String;
@@ -45,8 +47,12 @@ public final class io/tolgee/Tolgee$Config {
4547
public final fun component1 ()Ljava/util/Locale;
4648
public final fun component2 ()Lio/tolgee/Tolgee$Config$Network;
4749
public final fun component3 ()Lio/tolgee/Tolgee$Config$ContentDelivery;
50+
public final fun component4 ()Ljava/util/List;
51+
public final fun component5 ()Ljava/util/Locale;
4852
public fun equals (Ljava/lang/Object;)Z
53+
public final fun getAvailableLocales ()Ljava/util/List;
4954
public final fun getContentDelivery ()Lio/tolgee/Tolgee$Config$ContentDelivery;
55+
public final fun getDefaultLanguage ()Ljava/util/Locale;
5056
public final fun getLocale ()Ljava/util/Locale;
5157
public final fun getNetwork ()Lio/tolgee/Tolgee$Config$Network;
5258
public fun hashCode ()I
@@ -55,20 +61,30 @@ public final class io/tolgee/Tolgee$Config {
5561

5662
public final class io/tolgee/Tolgee$Config$Builder {
5763
public fun <init> ()V
64+
public final fun availableLocaleTags (Ljava/util/List;)Lio/tolgee/Tolgee$Config$Builder;
65+
public final fun availableLocaleTags ([Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
66+
public final fun availableLocales (Ljava/util/List;)Lio/tolgee/Tolgee$Config$Builder;
67+
public final fun availableLocales ([Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
5868
public final fun build ()Lio/tolgee/Tolgee$Config;
5969
public final fun contentDelivery (Lio/tolgee/Tolgee$Config$ContentDelivery;)Lio/tolgee/Tolgee$Config$Builder;
6070
public final fun contentDelivery (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
6171
public final fun contentDelivery (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
6272
public final fun contentDelivery (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
6373
public static synthetic fun contentDelivery$default (Lio/tolgee/Tolgee$Config$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/tolgee/Tolgee$Config$Builder;
74+
public final fun defaultLanguage (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
75+
public final fun defaultLanguage (Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
76+
public final fun getAvailableLocales ()Ljava/util/List;
6477
public final fun getContentDelivery ()Lio/tolgee/Tolgee$Config$ContentDelivery;
78+
public final fun getDefaultLanguage ()Ljava/util/Locale;
6579
public final fun getLocale ()Ljava/util/Locale;
6680
public final fun getNetwork ()Lio/tolgee/Tolgee$Config$Network;
6781
public final fun locale (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
6882
public final fun locale (Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
6983
public final fun network (Lio/tolgee/Tolgee$Config$Network;)Lio/tolgee/Tolgee$Config$Builder;
7084
public final fun network (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
85+
public final fun setAvailableLocales (Ljava/util/List;)V
7186
public final fun setContentDelivery (Lio/tolgee/Tolgee$Config$ContentDelivery;)V
87+
public final fun setDefaultLanguage (Ljava/util/Locale;)V
7288
public final fun setLocale (Ljava/util/Locale;)V
7389
public final fun setNetwork (Lio/tolgee/Tolgee$Config$Network;)V
7490
}
@@ -83,8 +99,12 @@ public final class io/tolgee/Tolgee$Config$ContentDelivery {
8399
public final fun component2 ()Lkotlin/jvm/functions/Function1;
84100
public final fun component3 ()Lio/tolgee/storage/TolgeeStorageProvider;
85101
public final fun component4 ()Lio/tolgee/Tolgee$Formatter;
102+
public final fun component5 ()Ljava/lang/String;
103+
public final fun component6 ()Ljava/lang/Integer;
86104
public fun equals (Ljava/lang/Object;)Z
87105
public final fun getFormatter ()Lio/tolgee/Tolgee$Formatter;
106+
public final fun getManifestPath ()Ljava/lang/String;
107+
public final fun getMaxLocalesInMemory ()Ljava/lang/Integer;
88108
public final fun getPath ()Lkotlin/jvm/functions/Function1;
89109
public final fun getStorage ()Lio/tolgee/storage/TolgeeStorageProvider;
90110
public final fun getUrl ()Ljava/lang/String;
@@ -97,11 +117,17 @@ public final class io/tolgee/Tolgee$Config$ContentDelivery$Builder {
97117
public final fun build ()Lio/tolgee/Tolgee$Config$ContentDelivery;
98118
public final fun formatter (Lio/tolgee/Tolgee$Formatter;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
99119
public final fun getFormatter ()Lio/tolgee/Tolgee$Formatter;
120+
public final fun getManifestPath ()Ljava/lang/String;
121+
public final fun getMaxLocalesInMemory ()Ljava/lang/Integer;
100122
public final fun getPath ()Lkotlin/jvm/functions/Function1;
101123
public final fun getStorage ()Lio/tolgee/storage/TolgeeStorageProvider;
102124
public final fun getUrl ()Ljava/lang/String;
125+
public final fun manifestPath (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
126+
public final fun maxLocalesInMemory (Ljava/lang/Integer;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
103127
public final fun path (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
104128
public final fun setFormatter (Lio/tolgee/Tolgee$Formatter;)V
129+
public final fun setManifestPath (Ljava/lang/String;)V
130+
public final fun setMaxLocalesInMemory (Ljava/lang/Integer;)V
105131
public final fun setPath (Lkotlin/jvm/functions/Function1;)V
106132
public final fun setStorage (Lio/tolgee/storage/TolgeeStorageProvider;)V
107133
public final fun setUrl (Ljava/lang/String;)V
@@ -163,6 +189,9 @@ public final class io/tolgee/TolgeeAndroid : io/tolgee/Tolgee {
163189
public fun getConfig ()Lio/tolgee/Tolgee$Config;
164190
public fun hashCode ()I
165191
public final fun preload (Landroidx/lifecycle/LifecycleOwner;)Lkotlinx/coroutines/Job;
192+
public final fun preloadAll (Landroidx/lifecycle/LifecycleOwner;)Lkotlinx/coroutines/Job;
193+
public final fun retranslate (Landroid/app/Activity;)V
194+
public final fun retranslate (Landroid/view/View;)V
166195
public final fun t (Landroid/content/Context;I)Ljava/lang/String;
167196
public final fun t (Landroid/content/Context;I[Ljava/lang/Object;)Ljava/lang/String;
168197
public final fun t (Landroid/content/res/Resources;I)Ljava/lang/String;
@@ -190,9 +219,14 @@ public final class io/tolgee/TolgeeAndroid$Companion {
190219

191220
public final class io/tolgee/TolgeeContextWrapper : android/content/ContextWrapper {
192221
public static final field Companion Lio/tolgee/TolgeeContextWrapper$Companion;
193-
public fun <init> (Landroid/content/Context;Lio/tolgee/Tolgee;)V
222+
public fun <init> (Landroid/content/Context;Lio/tolgee/Tolgee;ZZZ)V
223+
public synthetic fun <init> (Landroid/content/Context;Lio/tolgee/Tolgee;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
224+
public final fun getArgumentLayoutInflater ()Z
194225
public final fun getBase ()Landroid/content/Context;
226+
public final fun getInterceptGetString ()Z
227+
public final fun getInterceptGetText ()Z
195228
public fun getResources ()Landroid/content/res/Resources;
229+
public fun getSystemService (Ljava/lang/String;)Ljava/lang/Object;
196230
public final fun getTolgee ()Lio/tolgee/Tolgee;
197231
public static final fun wrap (Landroid/content/Context;)Landroid/content/ContextWrapper;
198232
public static final fun wrap (Landroid/content/Context;Lio/tolgee/Tolgee;)Landroid/content/ContextWrapper;

core/api/jvm/core.api

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public class io/tolgee/Tolgee {
1313
public static final fun new (Lio/tolgee/Tolgee$Config;)Lio/tolgee/common/PlatformTolgee;
1414
public static final fun new (Lkotlin/jvm/functions/Function1;)Lio/tolgee/common/PlatformTolgee;
1515
public fun preload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
16+
public fun preloadAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1617
public final fun removeChangeListener (Lio/tolgee/Tolgee$ChangeListener;)Z
18+
protected final fun resolveLocale (Ljava/util/Locale;)Ljava/util/Locale;
1719
public fun setLocale (Ljava/lang/String;)Ljava/util/Locale;
1820
public fun setLocale (Ljava/util/Locale;)Ljava/util/Locale;
1921
public final fun t (Ljava/lang/String;)Ljava/lang/String;
@@ -45,8 +47,12 @@ public final class io/tolgee/Tolgee$Config {
4547
public final fun component1 ()Ljava/util/Locale;
4648
public final fun component2 ()Lio/tolgee/Tolgee$Config$Network;
4749
public final fun component3 ()Lio/tolgee/Tolgee$Config$ContentDelivery;
50+
public final fun component4 ()Ljava/util/List;
51+
public final fun component5 ()Ljava/util/Locale;
4852
public fun equals (Ljava/lang/Object;)Z
53+
public final fun getAvailableLocales ()Ljava/util/List;
4954
public final fun getContentDelivery ()Lio/tolgee/Tolgee$Config$ContentDelivery;
55+
public final fun getDefaultLanguage ()Ljava/util/Locale;
5056
public final fun getLocale ()Ljava/util/Locale;
5157
public final fun getNetwork ()Lio/tolgee/Tolgee$Config$Network;
5258
public fun hashCode ()I
@@ -55,20 +61,30 @@ public final class io/tolgee/Tolgee$Config {
5561

5662
public final class io/tolgee/Tolgee$Config$Builder {
5763
public fun <init> ()V
64+
public final fun availableLocaleTags (Ljava/util/List;)Lio/tolgee/Tolgee$Config$Builder;
65+
public final fun availableLocaleTags ([Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
66+
public final fun availableLocales (Ljava/util/List;)Lio/tolgee/Tolgee$Config$Builder;
67+
public final fun availableLocales ([Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
5868
public final fun build ()Lio/tolgee/Tolgee$Config;
5969
public final fun contentDelivery (Lio/tolgee/Tolgee$Config$ContentDelivery;)Lio/tolgee/Tolgee$Config$Builder;
6070
public final fun contentDelivery (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
6171
public final fun contentDelivery (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
6272
public final fun contentDelivery (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
6373
public static synthetic fun contentDelivery$default (Lio/tolgee/Tolgee$Config$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/tolgee/Tolgee$Config$Builder;
74+
public final fun defaultLanguage (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
75+
public final fun defaultLanguage (Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
76+
public final fun getAvailableLocales ()Ljava/util/List;
6477
public final fun getContentDelivery ()Lio/tolgee/Tolgee$Config$ContentDelivery;
78+
public final fun getDefaultLanguage ()Ljava/util/Locale;
6579
public final fun getLocale ()Ljava/util/Locale;
6680
public final fun getNetwork ()Lio/tolgee/Tolgee$Config$Network;
6781
public final fun locale (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$Builder;
6882
public final fun locale (Ljava/util/Locale;)Lio/tolgee/Tolgee$Config$Builder;
6983
public final fun network (Lio/tolgee/Tolgee$Config$Network;)Lio/tolgee/Tolgee$Config$Builder;
7084
public final fun network (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$Builder;
85+
public final fun setAvailableLocales (Ljava/util/List;)V
7186
public final fun setContentDelivery (Lio/tolgee/Tolgee$Config$ContentDelivery;)V
87+
public final fun setDefaultLanguage (Ljava/util/Locale;)V
7288
public final fun setLocale (Ljava/util/Locale;)V
7389
public final fun setNetwork (Lio/tolgee/Tolgee$Config$Network;)V
7490
}
@@ -83,8 +99,12 @@ public final class io/tolgee/Tolgee$Config$ContentDelivery {
8399
public final fun component2 ()Lkotlin/jvm/functions/Function1;
84100
public final fun component3 ()Lio/tolgee/storage/TolgeeStorageProvider;
85101
public final fun component4 ()Lio/tolgee/Tolgee$Formatter;
102+
public final fun component5 ()Ljava/lang/String;
103+
public final fun component6 ()Ljava/lang/Integer;
86104
public fun equals (Ljava/lang/Object;)Z
87105
public final fun getFormatter ()Lio/tolgee/Tolgee$Formatter;
106+
public final fun getManifestPath ()Ljava/lang/String;
107+
public final fun getMaxLocalesInMemory ()Ljava/lang/Integer;
88108
public final fun getPath ()Lkotlin/jvm/functions/Function1;
89109
public final fun getStorage ()Lio/tolgee/storage/TolgeeStorageProvider;
90110
public final fun getUrl ()Ljava/lang/String;
@@ -97,11 +117,17 @@ public final class io/tolgee/Tolgee$Config$ContentDelivery$Builder {
97117
public final fun build ()Lio/tolgee/Tolgee$Config$ContentDelivery;
98118
public final fun formatter (Lio/tolgee/Tolgee$Formatter;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
99119
public final fun getFormatter ()Lio/tolgee/Tolgee$Formatter;
120+
public final fun getManifestPath ()Ljava/lang/String;
121+
public final fun getMaxLocalesInMemory ()Ljava/lang/Integer;
100122
public final fun getPath ()Lkotlin/jvm/functions/Function1;
101123
public final fun getStorage ()Lio/tolgee/storage/TolgeeStorageProvider;
102124
public final fun getUrl ()Ljava/lang/String;
125+
public final fun manifestPath (Ljava/lang/String;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
126+
public final fun maxLocalesInMemory (Ljava/lang/Integer;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
103127
public final fun path (Lkotlin/jvm/functions/Function1;)Lio/tolgee/Tolgee$Config$ContentDelivery$Builder;
104128
public final fun setFormatter (Lio/tolgee/Tolgee$Formatter;)V
129+
public final fun setManifestPath (Ljava/lang/String;)V
130+
public final fun setMaxLocalesInMemory (Ljava/lang/Integer;)V
105131
public final fun setPath (Lkotlin/jvm/functions/Function1;)V
106132
public final fun setStorage (Lio/tolgee/storage/TolgeeStorageProvider;)V
107133
public final fun setUrl (Ljava/lang/String;)V

core/src/androidMain/kotlin/io/tolgee/TolgeeAndroid.kt

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.tolgee
22

3+
import android.app.Activity
34
import android.content.Context
45
import android.content.res.Resources
6+
import android.view.View
57
import androidx.annotation.AnyRes
68
import androidx.annotation.ArrayRes
79
import androidx.annotation.PluralsRes
@@ -216,6 +218,78 @@ data class TolgeeAndroid internal constructor(
216218
preload()
217219
}
218220

221+
/**
222+
* Preloads all available languages and their translations into memory.
223+
*
224+
* This is a convenience method that launches a coroutine in the provided [LifecycleOwner]'s
225+
* lifecycle scope and calls the suspend [preloadAll] function.
226+
*
227+
* This method loads translations for all locales defined in the manifest or configuration.
228+
* Translations are loaded into the LRU cache according to the configured cache size limit
229+
* (see [Config.ContentDelivery.maxLocalesInMemory]).
230+
*
231+
* Use cases:
232+
* - Applications that support frequent locale switching
233+
* - Offline-first applications that want to cache multiple languages
234+
* - Improving performance by preloading translations at app startup
235+
*
236+
* @param lifecycleOwner any [LifecycleOwner] to launch the coroutine from, e.g. Activity or Fragment
237+
* @see preloadAll For the base suspend function
238+
* @see preload For loading only the current locale
239+
*/
240+
fun preloadAll(lifecycleOwner: LifecycleOwner) = lifecycleOwner.lifecycleScope.launch {
241+
preloadAll()
242+
}
243+
244+
/**
245+
* Re-translates all views in the given view hierarchy that were automatically
246+
* translated during layout inflation.
247+
*
248+
* This method walks the view hierarchy and re-applies translations to any views
249+
* that have stored resource IDs from the [TolgeeLayoutInflaterFactory].
250+
*
251+
* Use this after language changes if you prefer not to recreate the Activity.
252+
* It provides a smoother UX than `Activity.recreate()` by avoiding the full
253+
* Activity lifecycle restart.
254+
*
255+
* Example usage:
256+
* ```kotlin
257+
* lifecycleScope.launch {
258+
* tolgee.changeFlow.collect {
259+
* tolgee.retranslate(this@MainActivity)
260+
* }
261+
* }
262+
* ```
263+
*
264+
* @param rootView The root view to start re-translation from (typically content view)
265+
* @see retranslate(Activity) For a convenience method that finds the content view automatically
266+
*/
267+
fun retranslate(rootView: View) {
268+
TolgeeLayoutInflaterFactory.retranslateViewHierarchy(rootView, this)
269+
}
270+
271+
/**
272+
* Re-translates all views in the Activity's content view.
273+
*
274+
* This is a convenience method that automatically finds the Activity's content view
275+
* (android.R.id.content) and re-translates all views in that hierarchy.
276+
*
277+
* Example usage:
278+
* ```kotlin
279+
* lifecycleScope.launch {
280+
* tolgee.changeFlow.collect {
281+
* tolgee.retranslate(this@MainActivity)
282+
* }
283+
* }
284+
* ```
285+
*
286+
* @param activity The activity whose views should be re-translated
287+
* @see retranslate(View) For the base method that accepts a root view
288+
*/
289+
fun retranslate(activity: Activity) {
290+
activity.findViewById<View>(android.R.id.content)?.let { retranslate(it) }
291+
}
292+
219293
/**
220294
* A companion object for utility functions related to string resources and key retrieval.
221295
*/

0 commit comments

Comments
 (0)