feat(Networking): enhance offline handling and localization for diary and members data
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Added `ACCESS_NETWORK_STATE` permission in AndroidManifest.xml to monitor network connectivity. - Introduced `NetworkConnectivityHolder` to manage network state and trigger data refresh when connectivity is restored. - Updated `DiaryManager` and `MembersManager` to support offline caching, allowing users to view previously loaded data when offline. - Enhanced localization by adding new keys for offline cache hints in both German and English, improving user experience during connectivity issues. - Updated UI components to display offline cache messages, ensuring users are informed when data is being served from cache.
This commit is contained in:
@@ -58,8 +58,8 @@ android {
|
||||
applicationId = "de.tsschulz.tt_tagebuch"
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
versionCode = 4
|
||||
versionName = "1.2.0"
|
||||
versionCode = libs.versions.appVersionCode.get().toInt()
|
||||
versionName = libs.versions.appVersionName.get()
|
||||
buildConfigField("String", "BACKEND_BASE_URL", "\"$backendBaseUrl\"")
|
||||
buildConfigField("String", "SOCKET_BASE_URL", "\"$socketBaseUrl\"")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:name=".app.MainApplication"
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.tsschulz.tt_tagebuch.app
|
||||
|
||||
import android.content.Context
|
||||
import de.tsschulz.tt_tagebuch.BuildConfig
|
||||
import de.tsschulz.tt_tagebuch.app.net.NetworkConnectivityHolder
|
||||
import de.tsschulz.tt_tagebuch.shared.api.BillingApi
|
||||
import de.tsschulz.tt_tagebuch.shared.api.CalendarHolidayApi
|
||||
import de.tsschulz.tt_tagebuch.shared.api.AccidentApi
|
||||
@@ -68,6 +69,7 @@ class AppDependencies(context: Context) {
|
||||
* (z. B. Club wählen → anderer Screen), im Gegensatz zu [rememberCoroutineScope].
|
||||
*/
|
||||
val applicationScope = CoroutineScope(applicationJob + Dispatchers.Main.immediate)
|
||||
val networkConnectivity = NetworkConnectivityHolder(context.applicationContext)
|
||||
val apiConfig = ApiConfig(baseUrl = BuildConfig.BACKEND_BASE_URL)
|
||||
val unauthorizedEvents = MutableStateFlow(0)
|
||||
private val tokenProvider = MutableTokenProvider()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.tsschulz.tt_tagebuch.app.net
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* Beobachtet die Standard-Netzwerkverbindung (z. B. für Reconnect-Refresh von Tagebuch/Mitgliedern).
|
||||
*/
|
||||
class NetworkConnectivityHolder(context: Context) {
|
||||
private val appContext = context.applicationContext
|
||||
private val cm = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val _connected = MutableStateFlow(readConnected())
|
||||
val connected: StateFlow<Boolean> = _connected.asStateFlow()
|
||||
|
||||
private val callback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
_connected.value = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
_connected.value = readConnected()
|
||||
}
|
||||
|
||||
override fun onAvailable(network: Network) {
|
||||
_connected.value = readConnected()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
cm.registerDefaultNetworkCallback(callback)
|
||||
_connected.value = readConnected()
|
||||
}
|
||||
|
||||
private fun readConnected(): Boolean {
|
||||
val network = cm.activeNetwork ?: return false
|
||||
val caps = cm.getNetworkCapabilities(network) ?: return false
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,40 @@ private fun MainTabs(dependencies: AppDependencies) {
|
||||
val useWideMainNav = LocalConfiguration.current.screenWidthDp >= MAIN_NAV_RAIL_MIN_WIDTH_DP
|
||||
val clubState by dependencies.clubManager.state.collectAsState()
|
||||
val visibleTabs = visibleMainTabs(clubState.currentPermissions)
|
||||
val networkConnected by dependencies.networkConnectivity.connected.collectAsState()
|
||||
var lastNetworkConnected by remember { mutableStateOf(true) }
|
||||
|
||||
/** Tagebuch- und Mitglieder-Listen vorladen, sobald Verein + Rechte feststehen. */
|
||||
LaunchedEffect(clubState.currentClubId, clubState.currentPermissions) {
|
||||
val id = clubState.currentClubId ?: return@LaunchedEffect
|
||||
val perms = clubState.currentPermissions ?: return@LaunchedEffect
|
||||
dependencies.applicationScope.launch {
|
||||
if (perms.canReadDiary()) {
|
||||
dependencies.diaryManager.loadDates(id)
|
||||
}
|
||||
if (perms.canReadMembers()) {
|
||||
dependencies.membersManager.loadMembers(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Nach Netzwerk-Wiederkehr Listen neu laden (Server ist Quelle der Wahrheit). */
|
||||
LaunchedEffect(networkConnected, clubState.currentClubId, clubState.currentPermissions) {
|
||||
val id = clubState.currentClubId ?: return@LaunchedEffect
|
||||
val perms = clubState.currentPermissions ?: return@LaunchedEffect
|
||||
val wasConnected = lastNetworkConnected
|
||||
lastNetworkConnected = networkConnected
|
||||
if (!wasConnected && networkConnected) {
|
||||
dependencies.applicationScope.launch {
|
||||
if (perms.canReadDiary()) {
|
||||
dependencies.diaryManager.loadDates(id)
|
||||
}
|
||||
if (perms.canReadMembers()) {
|
||||
dependencies.membersManager.loadMembers(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(selectedTab) {
|
||||
if (selectedTab != MainTab.Settings) {
|
||||
@@ -1462,6 +1496,14 @@ private fun DiaryListScreen(
|
||||
.padding(horizontal = ScreenHorizontalPadding, vertical = 16.dp),
|
||||
) {
|
||||
Header(tr("diary.title", "Tagebuch"))
|
||||
if (diaryState.fromOfflineCache) {
|
||||
Text(
|
||||
tr("mobile.offlineCacheHint", "Keine Verbindung – zuletzt geladene Daten."),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.65f),
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
tr("diary.listHintCompact", "Trainingstag wählen und direkt bearbeiten."),
|
||||
style = MaterialTheme.typography.body2,
|
||||
@@ -4502,6 +4544,14 @@ private fun MembersScreen(
|
||||
.padding(horizontal = ScreenHorizontalPadding, vertical = 16.dp),
|
||||
) {
|
||||
Header(tr("members.title", "Mitglieder"))
|
||||
if (membersState.fromOfflineCache) {
|
||||
Text(
|
||||
trStr("mobile.offlineCacheHint", "Keine Verbindung – zuletzt geladene Daten."),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.65f),
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[versions]
|
||||
# composeApp (Play Store / „Über die App“-Build)
|
||||
appVersionCode = "5"
|
||||
appVersionName = "1.2.1"
|
||||
agp = "9.2.1"
|
||||
android-compileSdk = "35"
|
||||
android-minSdk = "24"
|
||||
|
||||
@@ -1341,6 +1341,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -3853,6 +3854,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -6366,6 +6368,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -8872,6 +8875,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -11378,6 +11382,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -13884,6 +13889,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -16390,6 +16396,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Sin conexión: se muestran los datos cargados por última vez.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -18896,6 +18903,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Walang koneksyon – ipinapakita ang huling na-load na datos.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -21402,6 +21410,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Pas de connexion – affichage des données du dernier chargement.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -23908,6 +23917,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Nessuna connessione – dati dell’ultimo caricamento.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -26414,6 +26424,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "ネットワークに接続されていません。最後に読み込んだデータを表示しています。",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -28920,6 +28931,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Brak połączenia – wyświetlane ostatnio wczytane dane.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -31426,6 +31438,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "ไม่มีการเชื่อมต่อ – แสดงข้อมูลที่โหลดล่าสุด",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -33932,6 +33945,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Walang koneksyon – ipinapakita ang huling na-load na datos.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -36438,6 +36452,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "无网络连接,显示上次加载的数据。",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
|
||||
@@ -224,12 +224,32 @@ class DiaryManager(
|
||||
}
|
||||
|
||||
suspend fun loadDates(clubId: Int) {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
val prev = _state.value
|
||||
val base = if (prev.lastLoadedClubId != null && prev.lastLoadedClubId != clubId) {
|
||||
prev.copy(dates = emptyList(), lastLoadedClubId = null, fromOfflineCache = false, error = null)
|
||||
} else {
|
||||
prev
|
||||
}
|
||||
_state.value = base.copy(isLoading = true, error = null)
|
||||
try {
|
||||
val dates = diaryApi.listDates(clubId)
|
||||
_state.value = _state.value.copy(isLoading = false, dates = dates)
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
dates = dates,
|
||||
lastLoadedClubId = clubId,
|
||||
fromOfflineCache = false,
|
||||
error = null,
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
_state.value = _state.value.copy(isLoading = false, error = t.toUserMessage("Fehler beim Laden des Tagebuchs"))
|
||||
if (base.lastLoadedClubId == clubId && base.dates.isNotEmpty()) {
|
||||
_state.value = base.copy(isLoading = false, fromOfflineCache = true, error = null)
|
||||
} else {
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
error = t.toUserMessage("Fehler beim Laden des Tagebuchs"),
|
||||
fromOfflineCache = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,9 @@ data class DiaryState(
|
||||
val dates: List<DiaryDate> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
/** Zuletzt erfolgreich geladener Verein (für Offline-Cache). */
|
||||
val lastLoadedClubId: Int? = null,
|
||||
/** `true`, wenn die angezeigte Tagebuch-Liste von einem fehlgeschlagenen Refresh stammt. */
|
||||
val fromOfflineCache: Boolean = false,
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,13 @@ class MembersManager(
|
||||
val state: StateFlow<MembersState> = _state.asStateFlow()
|
||||
|
||||
suspend fun loadMembers(clubId: Int) {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
val prev = _state.value
|
||||
val base = if (prev.lastLoadedClubId != null && prev.lastLoadedClubId != clubId) {
|
||||
prev.copy(members = emptyList(), lastLoadedClubId = null, fromOfflineCache = false, error = null)
|
||||
} else {
|
||||
prev
|
||||
}
|
||||
_state.value = base.copy(isLoading = true, error = null)
|
||||
try {
|
||||
val members = membersApi.listMembers(clubId)
|
||||
val merged = runCatching { trainingStatsApi.getStats(clubId) }
|
||||
@@ -41,9 +47,23 @@ class MembersManager(
|
||||
onFailure = { members },
|
||||
)
|
||||
.sortedWith(compareBy<Member> { it.lastName.lowercase() }.thenBy { it.firstName.lowercase() })
|
||||
_state.value = _state.value.copy(members = merged, isLoading = false)
|
||||
_state.value = base.copy(
|
||||
members = merged,
|
||||
isLoading = false,
|
||||
lastLoadedClubId = clubId,
|
||||
fromOfflineCache = false,
|
||||
error = null,
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
_state.value = _state.value.copy(isLoading = false, error = t.toUserMessage("Mitglieder konnten nicht geladen werden"))
|
||||
if (base.lastLoadedClubId == clubId && base.members.isNotEmpty()) {
|
||||
_state.value = base.copy(isLoading = false, fromOfflineCache = true, error = null)
|
||||
} else {
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
error = t.toUserMessage("Mitglieder konnten nicht geladen werden"),
|
||||
fromOfflineCache = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,7 @@ data class MembersState(
|
||||
val members: List<Member> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val lastLoadedClubId: Int? = null,
|
||||
val fromOfflineCache: Boolean = false,
|
||||
)
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ const MOBILE_STRINGS = {
|
||||
'mobile.noMembers': 'Keine Mitglieder gefunden',
|
||||
'mobile.noResults': 'Keine Treffer',
|
||||
'mobile.noTimes': 'Keine Zeiten',
|
||||
'mobile.offlineCacheHint': 'Keine Verbindung – zuletzt geladene Daten.',
|
||||
'mobile.participationTop': 'Top Teilnahmen',
|
||||
'mobile.refresh': 'Aktualisieren',
|
||||
'mobile.requestedAccess': 'Angefragt',
|
||||
@@ -115,6 +116,7 @@ const MOBILE_STRINGS = {
|
||||
'mobile.noMembers': 'No members found',
|
||||
'mobile.noResults': 'No results',
|
||||
'mobile.noTimes': 'No times',
|
||||
'mobile.offlineCacheHint': 'No connection – showing last loaded data.',
|
||||
'mobile.participationTop': 'Top attendance',
|
||||
'mobile.refresh': 'Refresh',
|
||||
'mobile.requestedAccess': 'Requested',
|
||||
@@ -131,6 +133,33 @@ const MOBILE_STRINGS = {
|
||||
'mobile.inactive': 'Inactive',
|
||||
'mobile.user': 'User',
|
||||
},
|
||||
es: {
|
||||
'mobile.offlineCacheHint': 'Sin conexión: se muestran los datos cargados por última vez.',
|
||||
},
|
||||
fr: {
|
||||
'mobile.offlineCacheHint': 'Pas de connexion – affichage des données du dernier chargement.',
|
||||
},
|
||||
it: {
|
||||
'mobile.offlineCacheHint': 'Nessuna connessione – dati dell’ultimo caricamento.',
|
||||
},
|
||||
pl: {
|
||||
'mobile.offlineCacheHint': 'Brak połączenia – wyświetlane ostatnio wczytane dane.',
|
||||
},
|
||||
ja: {
|
||||
'mobile.offlineCacheHint': 'ネットワークに接続されていません。最後に読み込んだデータを表示しています。',
|
||||
},
|
||||
th: {
|
||||
'mobile.offlineCacheHint': 'ไม่มีการเชื่อมต่อ – แสดงข้อมูลที่โหลดล่าสุด',
|
||||
},
|
||||
tl: {
|
||||
'mobile.offlineCacheHint': 'Walang koneksyon – ipinapakita ang huling na-load na datos.',
|
||||
},
|
||||
fil: {
|
||||
'mobile.offlineCacheHint': 'Walang koneksyon – ipinapakita ang huling na-load na datos.',
|
||||
},
|
||||
zh: {
|
||||
'mobile.offlineCacheHint': '无网络连接,显示上次加载的数据。',
|
||||
},
|
||||
};
|
||||
|
||||
/** Kalender-Tab (CalendarScreen.kt): eigene Übersetzungen pro Locale, damit nicht nur Deutsch aus dem Basis-Flat greift. */
|
||||
|
||||
Reference in New Issue
Block a user