diff --git a/crowdin/src/main/java/com/crowdin/platform/Crowdin.kt b/crowdin/src/main/java/com/crowdin/platform/Crowdin.kt index 86f0b9b..a13e347 100755 --- a/crowdin/src/main/java/com/crowdin/platform/Crowdin.kt +++ b/crowdin/src/main/java/com/crowdin/platform/Crowdin.kt @@ -17,8 +17,8 @@ import com.crowdin.platform.data.model.ApiAuthConfig import com.crowdin.platform.data.model.AuthConfig import com.crowdin.platform.data.model.AuthInfo import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.parser.StringResourceParser import com.crowdin.platform.data.parser.XmlReader import com.crowdin.platform.data.remote.Connectivity @@ -565,7 +565,12 @@ object Crowdin { fun getManifest(): ManifestData? = dataManager?.getManifest() - fun getSupportedLanguages(): LanguagesInfo? = dataManager?.getSupportedLanguages() + /** + * Get all supported languages from the distribution. + * @return Map where key is language code and value contains language details (name, locale). + * Example: `{"de": LanguageDetails("German", "de-DE"), "it": LanguageDetails("Italian", "it-IT")}` + */ + fun getSupportedLanguages(): SupportedLanguages? = dataManager?.getSupportedLanguages() private fun initDistributionInfo() { if (config.apiAuthConfig?.apiToken == null) { diff --git a/crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt b/crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt index b486cf0..a09a7d0 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt @@ -10,11 +10,13 @@ import com.crowdin.platform.Preferences import com.crowdin.platform.data.local.LocalRepository import com.crowdin.platform.data.model.ArrayData import com.crowdin.platform.data.model.AuthInfo +import com.crowdin.platform.data.model.CachedLanguages import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData import com.crowdin.platform.data.model.PluralData import com.crowdin.platform.data.model.StringData +import com.crowdin.platform.data.model.SupportedLanguages +import com.crowdin.platform.data.model.SyncData import com.crowdin.platform.data.model.TextMetaData import com.crowdin.platform.data.remote.Connectivity import com.crowdin.platform.data.remote.NetworkType @@ -32,20 +34,6 @@ internal class DataManager( private val crowdinPreferences: Preferences, private val dataChangeObserver: LocalDataChangeObserver, ) : TextMetaDataProvider { - companion object { - private const val STATUS_OK = "ok" - const val SUF_COPY = "-copy" - const val DISTRIBUTION_DATA = "distribution_data" - const val AUTH_INFO = "auth_info" - const val DISTRIBUTION_HASH = "distribution_hash" - const val MAPPING_SUF = "-mapping" - const val SUPPORTED_LANGUAGES = "supported_languages" - const val MANIFEST_DATA = "manifest_data" - const val SYNC_DATA = "sync_data" - const val EVENT_TICKETS = "event_tickets" - const val EVENT_TICKETS_EXPIRATION = 1000 * 60 * 4 - } - private var loadingStateListeners: ArrayList? = null override fun provideTextMetaData( @@ -216,7 +204,7 @@ internal class DataManager( } fun saveMapping(languageData: LanguageData) { - languageData.language = languageData.language + MAPPING_SUF + languageData.language += MAPPING_SUF localRepository.saveLanguageData(languageData) } @@ -266,18 +254,24 @@ internal class DataManager( } @WorkerThread - fun getSupportedLanguages(): LanguagesInfo? { + fun getSupportedLanguages(): SupportedLanguages? { Log.v(CROWDIN_TAG, "Getting supported languages started") - - var info: LanguagesInfo? = getData(SUPPORTED_LANGUAGES, LanguagesInfo::class.java) - if (info == null) { - info = remoteRepository.getSupportedLanguages() - saveData(SUPPORTED_LANGUAGES, info) + val syncData = crowdinPreferences.getData(SYNC_DATA, SyncData::class.java) + val cachedLanguages = getData(CACHED_LANGUAGES, CachedLanguages::class.java) + val currentTimestamp = syncData?.timestamp ?: 0L + + return if (cachedLanguages != null && cachedLanguages.manifestTimestamp == currentTimestamp) { + cachedLanguages.languages + } else { + val fetchedLanguages = remoteRepository.getSupportedLanguages() + if (fetchedLanguages != null) { + saveData(CACHED_LANGUAGES, CachedLanguages(fetchedLanguages, currentTimestamp)) + } else { + Log.v(CROWDIN_TAG, "Failed to fetch languages from distribution") + } + Log.v(CROWDIN_TAG, "Supported languages: $fetchedLanguages") + fetchedLanguages } - - Log.v(CROWDIN_TAG, "Supported languages: $info") - - return info } @WorkerThread @@ -320,4 +314,18 @@ internal class DataManager( ) { fun isExpired(): Boolean = System.currentTimeMillis() > expirationTime } + + companion object { + private const val STATUS_OK = "ok" + const val SUF_COPY = "-copy" + const val DISTRIBUTION_DATA = "distribution_data" + const val AUTH_INFO = "auth_info" + const val DISTRIBUTION_HASH = "distribution_hash" + const val MAPPING_SUF = "-mapping" + const val CACHED_LANGUAGES = "cached_languages" + const val MANIFEST_DATA = "manifest_data" + const val SYNC_DATA = "sync_data" + const val EVENT_TICKETS = "event_tickets" + const val EVENT_TICKETS_EXPIRATION = 1000 * 60 * 4 + } } diff --git a/crowdin/src/main/java/com/crowdin/platform/data/model/CachedLanguages.kt b/crowdin/src/main/java/com/crowdin/platform/data/model/CachedLanguages.kt new file mode 100644 index 0000000..a63ed4d --- /dev/null +++ b/crowdin/src/main/java/com/crowdin/platform/data/model/CachedLanguages.kt @@ -0,0 +1,6 @@ +package com.crowdin.platform.data.model + +internal data class CachedLanguages( + val languages: SupportedLanguages, + val manifestTimestamp: Long, +) diff --git a/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageInfoData.kt b/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageInfoData.kt deleted file mode 100644 index a094cc0..0000000 --- a/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageInfoData.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.crowdin.platform.data.model - -import com.google.gson.annotations.SerializedName - -data class LanguagesInfo( - @SerializedName("data") - val data: List, -) - -data class LanguageInfoData( - @SerializedName("data") - val data: LanguageInfo, -) - -data class LanguageInfo( - @SerializedName("id") - val id: String, - @SerializedName("name") - val name: String, - @SerializedName("twoLettersCode") - val twoLettersCode: String, - @SerializedName("threeLettersCode") - val threeLettersCode: String, - @SerializedName("locale") - val locale: String, - @SerializedName("androidCode") - val androidCode: String, -) diff --git a/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageMapper.kt b/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageMapper.kt deleted file mode 100644 index ab34b7e..0000000 --- a/crowdin/src/main/java/com/crowdin/platform/data/model/LanguageMapper.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.crowdin.platform.data.model - -fun CustomLanguage?.toLanguageInfo(): LanguageInfo? = - this?.let { - LanguageInfo( - id = locale, - name = name, - twoLettersCode = twoLettersCode, - threeLettersCode = threeLettersCode, - locale = locale, - androidCode = androidCode, - ) - } diff --git a/crowdin/src/main/java/com/crowdin/platform/data/model/ManifestData.kt b/crowdin/src/main/java/com/crowdin/platform/data/model/ManifestData.kt index 80dda7f..6c9a0f4 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/model/ManifestData.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/model/ManifestData.kt @@ -9,8 +9,6 @@ data class ManifestData( val timestamp: Long, @SerializedName("languages") val languages: List, - @SerializedName("custom_languages") - val customLanguages: Map, @SerializedName("language_mapping") val languageMapping: Map>, @SerializedName("content") @@ -18,18 +16,3 @@ data class ManifestData( @SerializedName("mapping") val mapping: List, ) - -data class CustomLanguage( - @SerializedName("name") - val name: String, - @SerializedName("two_letters_code") - val twoLettersCode: String, - @SerializedName("three_letters_code") - val threeLettersCode: String, - @SerializedName("locale") - val locale: String, - @SerializedName("android_code") - val androidCode: String, - @SerializedName("locale_with_underscore") - val localeWithUnderscore: String, -) diff --git a/crowdin/src/main/java/com/crowdin/platform/data/model/SupportedLanguages.kt b/crowdin/src/main/java/com/crowdin/platform/data/model/SupportedLanguages.kt new file mode 100644 index 0000000..6e12549 --- /dev/null +++ b/crowdin/src/main/java/com/crowdin/platform/data/model/SupportedLanguages.kt @@ -0,0 +1,17 @@ +package com.crowdin.platform.data.model + +import com.google.gson.annotations.SerializedName + +/** + * Map of supported languages from distribution API. + * Key: language code (e.g., "de", "de-BE", "it", "tra") + * Value: language details + */ +typealias SupportedLanguages = Map + +data class LanguageDetails( + @SerializedName("name") + val name: String, + @SerializedName("locale") + val locale: String, +) diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt index 5c59f54..45afb3a 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt @@ -5,9 +5,9 @@ import android.util.Log import androidx.annotation.WorkerThread import com.crowdin.platform.Crowdin import com.crowdin.platform.data.LanguageDataCallback -import com.crowdin.platform.data.model.LanguageInfo -import com.crowdin.platform.data.model.LanguagesInfo +import com.crowdin.platform.data.model.LanguageDetails import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.model.TicketRequestBody import com.crowdin.platform.data.model.TicketResponseBody import com.crowdin.platform.data.remote.api.CrowdinApi @@ -24,7 +24,7 @@ internal abstract class CrowdingRepository( private val distributionHash: String, ) : BaseRepository() { var crowdinApi: CrowdinApi? = null - var crowdinLanguages: LanguagesInfo? = null + var crowdinLanguages: SupportedLanguages? = null override fun getManifest( languageDataCallback: LanguageDataCallback?, @@ -87,15 +87,18 @@ internal abstract class CrowdingRepository( languageDataCallback: LanguageDataCallback?, ) - override fun getSupportedLanguages(): LanguagesInfo? { + override fun getSupportedLanguages(): SupportedLanguages? { Log.v(Crowdin.CROWDIN_TAG, "Getting supported languages from Api started") + var languages: SupportedLanguages? = null + executeIO { + val response = crowdinDistributionApi.getLanguages(distributionHash)?.execute()?.body() + if (response != null) { + languages = response + } + } + Log.v(Crowdin.CROWDIN_TAG, "Supported languages from Api: $languages") - var info: LanguagesInfo? = null - executeIO { info = crowdinApi?.getLanguagesInfo()?.execute()?.body() } - - Log.v(Crowdin.CROWDIN_TAG, "Supported languages from Api: $info") - - return info + return languages } @WorkerThread @@ -105,14 +108,5 @@ internal abstract class CrowdingRepository( return result } - fun getLanguageInfo(sourceLanguage: String): LanguageInfo? { - crowdinLanguages?.data?.forEach { - val languageInfo = it.data - if (languageInfo.id == sourceLanguage) { - return languageInfo - } - } - - return null - } + fun getLanguageInfo(sourceLanguage: String): LanguageDetails? = crowdinLanguages?.get(sourceLanguage) } diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/MappingRepository.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/MappingRepository.kt index 11f9997..122542e 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/MappingRepository.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/MappingRepository.kt @@ -8,8 +8,8 @@ import com.crowdin.platform.data.DataManager import com.crowdin.platform.data.DataManager.Companion.MANIFEST_DATA import com.crowdin.platform.data.LanguageDataCallback import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.parser.Reader import com.crowdin.platform.data.remote.api.CrowdinDistributionApi import com.crowdin.platform.util.executeIO @@ -23,14 +23,11 @@ internal class MappingRepository( private val dataManager: DataManager, private val distributionHash: String, private val sourceLanguage: String, -) : CrowdingRepository( - crowdinDistributionApi, - distributionHash, - ) { +) : CrowdingRepository(crowdinDistributionApi, distributionHash) { override fun fetchData( configuration: Configuration?, languageCode: String?, - supportedLanguages: LanguagesInfo?, + supportedLanguages: SupportedLanguages?, languageDataCallback: LanguageDataCallback?, ) { Log.v(Crowdin.CROWDIN_TAG, "MappingRepository. Fetch data from Api started") diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/RemoteRepository.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/RemoteRepository.kt index d300172..eaa8e18 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/RemoteRepository.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/RemoteRepository.kt @@ -4,8 +4,8 @@ import android.content.res.Configuration import androidx.annotation.WorkerThread import com.crowdin.platform.data.LanguageDataCallback import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.model.TicketResponseBody /** @@ -17,12 +17,13 @@ internal interface RemoteRepository { * * @param configuration device configuration for locale context. * @param languageCode code to be user for search data. + * @param supportedLanguages map of supported languages (code -> details). * @param languageDataCallback delivers data back to caller. */ fun fetchData( configuration: Configuration? = null, languageCode: String? = null, - supportedLanguages: LanguagesInfo? = null, + supportedLanguages: SupportedLanguages? = null, languageDataCallback: LanguageDataCallback? = null, ) @@ -32,10 +33,10 @@ internal interface RemoteRepository { ) /** - * Fetch all supported languages. + * Fetch all supported languages as a map (language code -> details). */ @WorkerThread - fun getSupportedLanguages(): LanguagesInfo? + fun getSupportedLanguages(): SupportedLanguages? /** * Fetch ticket for WebSocket connection. diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/StringDataRemoteRepository.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/StringDataRemoteRepository.kt index a951ec2..656040f 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/StringDataRemoteRepository.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/StringDataRemoteRepository.kt @@ -6,12 +6,10 @@ import com.crowdin.platform.Crowdin import com.crowdin.platform.Preferences import com.crowdin.platform.data.DataManager import com.crowdin.platform.data.LanguageDataCallback -import com.crowdin.platform.data.model.CustomLanguage import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.model.SyncData -import com.crowdin.platform.data.model.toLanguageInfo import com.crowdin.platform.data.parser.ReaderFactory import com.crowdin.platform.data.remote.api.CrowdinDistributionApi import com.crowdin.platform.util.executeIO @@ -28,16 +26,13 @@ internal class StringDataRemoteRepository( private val crowdinPreferences: Preferences, private val crowdinDistributionApi: CrowdinDistributionApi, private val distributionHash: String, -) : CrowdingRepository( - crowdinDistributionApi, - distributionHash, - ) { +) : CrowdingRepository(crowdinDistributionApi, distributionHash) { private var preferredLanguageCode: String? = null override fun fetchData( configuration: Configuration?, languageCode: String?, - supportedLanguages: LanguagesInfo?, + supportedLanguages: SupportedLanguages?, languageDataCallback: LanguageDataCallback?, ) { Log.v(Crowdin.CROWDIN_TAG, "StringDataRemoteRepository. Fetch data from Api started") @@ -71,8 +66,7 @@ internal class StringDataRemoteRepository( ) val supportedLanguages = manifest?.languages - val customLanguages = manifest?.customLanguages - val preferredLanguageCode = getSafeLanguageCode(configuration, preferredLanguageCode, supportedLanguages, customLanguages) + val preferredLanguageCode = getSafeLanguageCode(configuration, preferredLanguageCode, supportedLanguages) if (preferredLanguageCode == null) { languageDataCallback?.onFailure(Throwable("Can't find preferred Language")) return @@ -80,12 +74,12 @@ internal class StringDataRemoteRepository( // Combine all data before save to storage val languageData = LanguageData() - val languageInfo = - if (customLanguages?.contains(preferredLanguageCode) == true) { - customLanguages[preferredLanguageCode]?.toLanguageInfo() - } else { - getLanguageInfo(preferredLanguageCode) - } ?: return + val languageInfo = getLanguageInfo(preferredLanguageCode) + + if (languageInfo == null) { + languageDataCallback?.onFailure(Throwable("Language info not found for $preferredLanguageCode")) + return + } languageData.language = languageInfo.locale manifest?.content?.get(preferredLanguageCode)?.forEach { filePath -> @@ -108,10 +102,9 @@ internal class StringDataRemoteRepository( configuration: Configuration?, preferredLanguageCode: String?, supportedLanguages: List?, - customLanguages: Map?, ): String? = if (preferredLanguageCode == null) { - getMatchedCode(configuration, supportedLanguages, customLanguages) + getMatchedCode(configuration, supportedLanguages, crowdinLanguages) } else { if (supportedLanguages?.contains(preferredLanguageCode) == false) { null diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/TranslationDataRepository.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/TranslationDataRepository.kt index 212ca9a..58e9918 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/TranslationDataRepository.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/TranslationDataRepository.kt @@ -9,10 +9,9 @@ import com.crowdin.platform.data.LanguageDataCallback import com.crowdin.platform.data.model.BuildTranslationRequest import com.crowdin.platform.data.model.FileResponse import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.model.Translation -import com.crowdin.platform.data.model.toLanguageInfo import com.crowdin.platform.data.parser.Reader import com.crowdin.platform.data.remote.api.CrowdinDistributionApi import com.crowdin.platform.data.remote.api.CrowdinTranslationApi @@ -28,16 +27,13 @@ internal class TranslationDataRepository( private val reader: Reader, private val dataManager: DataManager, distributionHash: String, -) : CrowdingRepository( - crowdinDistributionApi, - distributionHash, - ) { +) : CrowdingRepository(crowdinDistributionApi, distributionHash) { private var preferredLanguageCode: String? = null override fun fetchData( configuration: Configuration?, languageCode: String?, - supportedLanguages: LanguagesInfo?, + supportedLanguages: SupportedLanguages?, languageDataCallback: LanguageDataCallback?, ) { Log.v(Crowdin.CROWDIN_TAG, "TranslationRepository. Fetch data from Api started") @@ -56,43 +52,41 @@ internal class TranslationDataRepository( ) { Log.v(Crowdin.CROWDIN_TAG, "Manifest data received") - val supportedLanguages = manifest?.languages - val customLanguages = manifest?.customLanguages + val languages = manifest?.languages + val languagesInfo = dataManager.getSupportedLanguages() + crowdinLanguages = languagesInfo + if (preferredLanguageCode == null) { - preferredLanguageCode = getMatchedCode(configuration, supportedLanguages, customLanguages) ?: return + preferredLanguageCode = getMatchedCode(configuration, languages, crowdinLanguages) ?: return } else { - if (supportedLanguages?.contains(preferredLanguageCode) == false) { + if (languages?.contains(preferredLanguageCode) == false) { + languageDataCallback?.onFailure(Throwable("Language info not found for $preferredLanguageCode")) return } } - val languagesInfo = dataManager.getSupportedLanguages() - crowdinLanguages = languagesInfo - val languageInfo = - if (customLanguages?.contains(preferredLanguageCode) == true) { - customLanguages[preferredLanguageCode]?.toLanguageInfo() - } else { - getLanguageInfo(preferredLanguageCode!!) - } + val languageInfo = getLanguageInfo(preferredLanguageCode!!) + if (languageInfo == null) { + languageDataCallback?.onFailure(Throwable("Language info not found for $preferredLanguageCode")) + return + } - languageInfo?.let { info -> - dataManager - .getData( - DataManager.DISTRIBUTION_DATA, - DistributionInfoResponse.DistributionData::class.java, - )?.project - ?.id - ?.let { - manifest?.files?.let { files -> - getFiles( - id = it, - files = files, - locale = info.locale, - languageDataCallback = languageDataCallback, - ) - } + dataManager + .getData( + DataManager.DISTRIBUTION_DATA, + DistributionInfoResponse.DistributionData::class.java, + )?.project + ?.id + ?.let { + manifest?.files?.let { files -> + getFiles( + id = it, + files = files, + locale = languageInfo.locale, + languageDataCallback = languageDataCallback, + ) } - } + } } private fun getFiles( diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinApi.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinApi.kt index 6cdc003..cad21eb 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinApi.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinApi.kt @@ -2,7 +2,6 @@ package com.crowdin.platform.data.remote.api import com.crowdin.platform.data.model.BuildTranslationRequest import com.crowdin.platform.data.model.FileResponse -import com.crowdin.platform.data.model.LanguagesInfo import com.crowdin.platform.data.model.ListScreenshotsResponse import com.crowdin.platform.data.model.TicketRequestBody import com.crowdin.platform.data.model.TicketResponseBody @@ -18,8 +17,6 @@ import retrofit2.http.PUT import retrofit2.http.Path import retrofit2.http.Query -private const val LANGUAGE_COUNT = 500 - internal interface CrowdinApi { @GET("/api/v2/projects/{projectId}/screenshots") fun getScreenshotsList( @@ -78,11 +75,6 @@ internal interface CrowdinApi { @Path("projectId") projectId: String, ): Call - @GET("/api/v2/languages") - fun getLanguagesInfo( - @Query("limit") limit: Int = LANGUAGE_COUNT, - ): Call - @POST("/api/v2/user/websocket-ticket") fun getTicket( @Body body: TicketRequestBody, diff --git a/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinDistributionApi.kt b/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinDistributionApi.kt index 9f167d6..411e774 100644 --- a/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinDistributionApi.kt +++ b/crowdin/src/main/java/com/crowdin/platform/data/remote/api/CrowdinDistributionApi.kt @@ -1,6 +1,7 @@ package com.crowdin.platform.data.remote.api import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages import okhttp3.ResponseBody import retrofit2.Call import retrofit2.http.GET @@ -28,4 +29,9 @@ internal interface CrowdinDistributionApi { @Path("distributionHash") distributionHash: String, @Path("filePath", encoded = true) filePath: String, ): Call + + @GET("/{distributionHash}/languages.json") + fun getLanguages( + @Path("distributionHash") distributionHash: String, + ): Call } diff --git a/crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt b/crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt index d473265..0f995d1 100644 --- a/crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt +++ b/crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt @@ -60,7 +60,8 @@ internal class RealTimeUpdateManager( .url(distributionData.wsUrl) .build() - val languageCode = getMatchedCode(configuration, it.languages, it.customLanguages) ?: return@let + val supportedLanguages = dataManager.getSupportedLanguages() + val languageCode = getMatchedCode(configuration, it.languages, supportedLanguages) ?: return@let val listener = EchoWebSocketListener( dataManager, diff --git a/crowdin/src/main/java/com/crowdin/platform/util/Extensions.kt b/crowdin/src/main/java/com/crowdin/platform/util/Extensions.kt index 257f2ca..9ffa74f 100644 --- a/crowdin/src/main/java/com/crowdin/platform/util/Extensions.kt +++ b/crowdin/src/main/java/com/crowdin/platform/util/Extensions.kt @@ -9,7 +9,7 @@ import android.view.Menu import android.view.MenuInflater import androidx.annotation.MenuRes import com.crowdin.platform.Crowdin -import com.crowdin.platform.data.model.CustomLanguage +import com.crowdin.platform.data.model.SupportedLanguages import java.io.IOException import java.text.SimpleDateFormat import java.util.Calendar @@ -59,13 +59,13 @@ fun Locale.toLanguageTagCompat(): String = fun getMatchedCode( configuration: Configuration?, list: List?, - customLanguages: Map?, + supportedLanguages: SupportedLanguages?, ): String? { val languageCode = configuration.getLocale().language.withCrowdinSupportedCheck() val code = "$languageCode-${Locale.getDefault().country}" - if (customLanguages != null) { - for (languageData in customLanguages) { + if (supportedLanguages != null) { + for (languageData in supportedLanguages) { if (languageData.value.locale == code) { return languageData.key } diff --git a/crowdin/src/test/java/com/crowdin/platform/CrowdingRepositoryTest.kt b/crowdin/src/test/java/com/crowdin/platform/CrowdingRepositoryTest.kt new file mode 100644 index 0000000..0dfde25 --- /dev/null +++ b/crowdin/src/test/java/com/crowdin/platform/CrowdingRepositoryTest.kt @@ -0,0 +1,115 @@ +package com.crowdin.platform + +import com.crowdin.platform.data.LanguageDataCallback +import com.crowdin.platform.data.model.LanguageDetails +import com.crowdin.platform.data.model.ManifestData +import com.crowdin.platform.data.model.SupportedLanguages +import com.crowdin.platform.data.remote.CrowdingRepository +import com.crowdin.platform.data.remote.api.CrowdinDistributionApi +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import retrofit2.Call +import retrofit2.Response + +class CrowdingRepositoryTest { + private lateinit var mockDistributionApi: CrowdinDistributionApi + private lateinit var testRepository: TestCrowdingRepository + + @Before + fun setUp() { + mockDistributionApi = mock(CrowdinDistributionApi::class.java) + testRepository = TestCrowdingRepository(mockDistributionApi, "testHash") + } + + @Test + fun whenGetSupportedLanguages_shouldCallDistributionApi() { + // Given + val mockCall = mock(Call::class.java) as Call + `when`(mockDistributionApi.getLanguages("testHash")).thenReturn(mockCall) + val languages = givenSupportedLanguages() + `when`(mockCall.execute()).thenReturn(Response.success(languages)) + + // When + testRepository.getSupportedLanguages() + + // Then + verify(mockDistributionApi).getLanguages("testHash") + } + + @Test + fun whenGetSupportedLanguages_shouldReturnMapOfLanguages() { + // Given + val mockCall = mock(Call::class.java) as Call + `when`(mockDistributionApi.getLanguages("testHash")).thenReturn(mockCall) + val expectedLanguages = givenSupportedLanguages() + `when`(mockCall.execute()).thenReturn(Response.success(expectedLanguages)) + + // When + val result = testRepository.getSupportedLanguages() + + // Then + assertThat(result, equalTo(expectedLanguages)) + assertThat(result?.size, equalTo(3)) + assertThat(result?.get("de")?.name, equalTo("German")) + assertThat(result?.get("it")?.locale, equalTo("it-IT")) + } + + @Test + fun whenGetLanguageInfo_shouldReturnLanguageDetailsDirectly() { + // Given + testRepository.crowdinLanguages = givenSupportedLanguages() + + // When + val result = testRepository.getLanguageInfo("de") + + // Then + assertThat(result, equalTo(LanguageDetails("German", "de-DE"))) + } + + @Test + fun whenGetLanguageInfo_withInvalidCode_shouldReturnNull() { + // Given + testRepository.crowdinLanguages = givenSupportedLanguages() + + // When + val result = testRepository.getLanguageInfo("fr") + + // Then + assertThat(result, equalTo(null)) + } + + private fun givenSupportedLanguages(): SupportedLanguages = + mapOf( + "de" to LanguageDetails("German", "de-DE"), + "it" to LanguageDetails("Italian", "it-IT"), + "en" to LanguageDetails("English", "en-US"), + ) + + // Test implementation of abstract class + private class TestCrowdingRepository( + crowdinDistributionApi: CrowdinDistributionApi, + distributionHash: String, + ) : CrowdingRepository(crowdinDistributionApi, distributionHash) { + override fun fetchData( + configuration: android.content.res.Configuration?, + languageCode: String?, + supportedLanguages: SupportedLanguages?, + languageDataCallback: LanguageDataCallback?, + ) { + // Test implementation + } + + override fun onManifestDataReceived( + configuration: android.content.res.Configuration?, + manifest: ManifestData?, + languageDataCallback: LanguageDataCallback?, + ) { + // Test implementation + } + } +} diff --git a/crowdin/src/test/java/com/crowdin/platform/DataManagerTest.kt b/crowdin/src/test/java/com/crowdin/platform/DataManagerTest.kt index 0acaaf2..5323833 100644 --- a/crowdin/src/test/java/com/crowdin/platform/DataManagerTest.kt +++ b/crowdin/src/test/java/com/crowdin/platform/DataManagerTest.kt @@ -4,10 +4,13 @@ import com.crowdin.platform.data.DataManager import com.crowdin.platform.data.local.LocalRepository import com.crowdin.platform.data.model.ArrayData import com.crowdin.platform.data.model.AuthInfo +import com.crowdin.platform.data.model.CachedLanguages import com.crowdin.platform.data.model.LanguageData +import com.crowdin.platform.data.model.LanguageDetails import com.crowdin.platform.data.model.ManifestData import com.crowdin.platform.data.model.PluralData import com.crowdin.platform.data.model.StringData +import com.crowdin.platform.data.model.SyncData import com.crowdin.platform.data.remote.RemoteRepository import com.crowdin.platform.util.FeatureFlags import com.crowdin.platform.util.getFormattedCode @@ -470,6 +473,108 @@ class DataManagerTest { verify(mockRemoteRepository).getManifest(any(), any()) } + @Test + fun whenGetSupportedLanguagesWithValidCache_shouldReturnCachedData() { + // Given + val dataManager = givenDataManager() + val currentTimestamp = 1234567890L + val syncData = SyncData(currentTimestamp, "en") + val cachedLanguages = + CachedLanguages( + mapOf("en" to LanguageDetails("English", "en-US")), + currentTimestamp, + ) + + `when`( + mockPreferences.getData( + eq("sync_data"), + eq(SyncData::class.java), + ), + ).thenReturn(syncData) + `when`( + mockLocalRepository.getData( + eq("cached_languages"), + eq(CachedLanguages::class.java), + ), + ).thenReturn(cachedLanguages) + + // When + val result = dataManager.getSupportedLanguages() + + // Then + assertThat(result?.size, `is`(1)) + assertThat(result?.get("en")?.name, `is`("English")) + verifyNoInteractions(mockRemoteRepository) + } + + @Test + fun whenGetSupportedLanguagesWithExpiredCache_shouldFetchFromApi() { + // Given + val dataManager = givenDataManager() + val oldTimestamp = 1000L + val newTimestamp = 2000L + val syncData = SyncData(newTimestamp, "de") + val cachedLanguages = + CachedLanguages( + mapOf("en" to LanguageDetails("English", "en-US")), + oldTimestamp, + ) + val freshLanguages = mapOf("de" to LanguageDetails("German", "de-DE")) + + `when`( + mockPreferences.getData( + eq("sync_data"), + eq(SyncData::class.java), + ), + ).thenReturn(syncData) + `when`( + mockLocalRepository.getData( + eq("cached_languages"), + eq(CachedLanguages::class.java), + ), + ).thenReturn(cachedLanguages) + `when`(mockRemoteRepository.getSupportedLanguages()).thenReturn(freshLanguages) + + // When + val result = dataManager.getSupportedLanguages() + + // Then + verify(mockRemoteRepository).getSupportedLanguages() + verify(mockLocalRepository).saveData(eq("cached_languages"), any()) + assertThat(result?.get("de")?.name, `is`("German")) + } + + @Test + fun whenGetSupportedLanguagesWithNoCache_shouldFetchFromApi() { + // Given + val dataManager = givenDataManager() + val timestamp = 1234567890L + val syncData = SyncData(timestamp, "it") + val freshLanguages = mapOf("it" to LanguageDetails("Italian", "it-IT")) + + `when`( + mockPreferences.getData( + eq("sync_data"), + eq(SyncData::class.java), + ), + ).thenReturn(syncData) + `when`( + mockLocalRepository.getData( + eq("cached_languages"), + eq(CachedLanguages::class.java), + ), + ).thenReturn(null) + `when`(mockRemoteRepository.getSupportedLanguages()).thenReturn(freshLanguages) + + // When + val result = dataManager.getSupportedLanguages() + + // Then + verify(mockRemoteRepository).getSupportedLanguages() + verify(mockLocalRepository).saveData(eq("cached_languages"), any()) + assertThat(result?.get("it")?.name, `is`("Italian")) + } + private fun givenDataManager(): DataManager = DataManager( mockRemoteRepository, diff --git a/crowdin/src/test/java/com/crowdin/platform/MappingRepositoryTest.kt b/crowdin/src/test/java/com/crowdin/platform/MappingRepositoryTest.kt index 032e475..6b0f8e0 100644 --- a/crowdin/src/test/java/com/crowdin/platform/MappingRepositoryTest.kt +++ b/crowdin/src/test/java/com/crowdin/platform/MappingRepositoryTest.kt @@ -3,9 +3,8 @@ package com.crowdin.platform import com.crowdin.platform.data.DataManager import com.crowdin.platform.data.LanguageDataCallback import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguageInfo -import com.crowdin.platform.data.model.LanguageInfoData -import com.crowdin.platform.data.model.LanguagesInfo +import com.crowdin.platform.data.model.LanguageDetails +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.parser.Reader import com.crowdin.platform.data.remote.MappingRepository import com.crowdin.platform.data.remote.api.CrowdinApi @@ -140,14 +139,14 @@ class MappingRepositoryTest { fun getLanguageInfoTest() { // Given val mappingRepository = givenMappingRepository() - val languageInfo = LanguageInfo("en", "name", "qq", "www", "en-US", "en-rUS") + val expectedLanguageDetails = LanguageDetails("English", "en-US") mappingRepository.crowdinLanguages = givenSupportedLanguages() // When - val actualLanguageInfo = mappingRepository.getLanguageInfo("en") + val actualLanguageDetails = mappingRepository.getLanguageInfo("en") // Then - assertThat(actualLanguageInfo, equalTo(languageInfo)) + assertThat(actualLanguageDetails, equalTo(expectedLanguageDetails)) } @Test @@ -157,10 +156,10 @@ class MappingRepositoryTest { mappingRepository.crowdinLanguages = givenSupportedLanguages() // When - val actualLanguageInfo = mappingRepository.getLanguageInfo("fr") + val actualLanguageDetails = mappingRepository.getLanguageInfo("fr") // Then - assertThat(actualLanguageInfo, equalTo(null)) + assertThat(actualLanguageDetails, equalTo(null)) } private fun givenMappingRepository(): MappingRepository { @@ -194,22 +193,11 @@ class MappingRepositoryTest { } private fun givenMockLanguageResponse() { - @Suppress("UNCHECKED_CAST") - val mockedCall = mock(Call::class.java) as Call - val response = - Response.success( - 200, - LanguageInfoData(givenLanguageInfo()), - ) - `when`(mockedCall.execute()).thenReturn(response) + // Language response not needed anymore with Map-based API } - private fun givenSupportedLanguages(): LanguagesInfo { - val languageInfo = LanguageInfo("en", "name", "qq", "www", "en-US", "en-rUS") - return LanguagesInfo( - mutableListOf(LanguageInfoData(languageInfo)), + private fun givenSupportedLanguages(): SupportedLanguages = + mapOf( + "en" to LanguageDetails("English", "en-US"), ) - } - - private fun givenLanguageInfo(): LanguageInfo = LanguageInfo("en", "English", "en", "eng", "en-US", "en-rUS") } diff --git a/crowdin/src/test/java/com/crowdin/platform/StringDataRemoteRepositoryTest.kt b/crowdin/src/test/java/com/crowdin/platform/StringDataRemoteRepositoryTest.kt index 0192d7d..d8c2953 100644 --- a/crowdin/src/test/java/com/crowdin/platform/StringDataRemoteRepositoryTest.kt +++ b/crowdin/src/test/java/com/crowdin/platform/StringDataRemoteRepositoryTest.kt @@ -1,9 +1,8 @@ package com.crowdin.platform import com.crowdin.platform.data.LanguageDataCallback -import com.crowdin.platform.data.model.LanguageInfo -import com.crowdin.platform.data.model.LanguageInfoData -import com.crowdin.platform.data.model.LanguagesInfo +import com.crowdin.platform.data.model.LanguageDetails +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.parser.Reader import com.crowdin.platform.data.remote.StringDataRemoteRepository import com.crowdin.platform.data.remote.api.CrowdinApi @@ -90,12 +89,7 @@ class StringDataRemoteRepositoryTest { verify(mockCallback).onFailure(any()) } - private fun givenSupportedLanguages(): LanguagesInfo { - val languageInfo = LanguageInfo("en", "name", "qq", "www", "en-US", "en-rUS") - return LanguagesInfo( - mutableListOf(LanguageInfoData(languageInfo)), - ) - } + private fun givenSupportedLanguages(): SupportedLanguages = mapOf("en" to LanguageDetails("English", "en-US")) private fun givenStringDataRemoteRepository(): StringDataRemoteRepository { val preferences = mock(Preferences::class.java) diff --git a/crowdin/src/test/java/com/crowdin/platform/TestCommon.kt b/crowdin/src/test/java/com/crowdin/platform/TestCommon.kt index a32f567..24410a3 100644 --- a/crowdin/src/test/java/com/crowdin/platform/TestCommon.kt +++ b/crowdin/src/test/java/com/crowdin/platform/TestCommon.kt @@ -60,7 +60,6 @@ internal fun givenMockManifestResponse( 123124154L, listOf(), hashMapOf(), - hashMapOf(), hashMapOf( "en" to listOf("/content/en/string.xml"), "de" to listOf("/content/de/string.xml"), diff --git a/crowdin/src/test/java/com/crowdin/platform/TranslationDataRepositoryTest.kt b/crowdin/src/test/java/com/crowdin/platform/TranslationDataRepositoryTest.kt index efc7a7f..a1a7fd6 100644 --- a/crowdin/src/test/java/com/crowdin/platform/TranslationDataRepositoryTest.kt +++ b/crowdin/src/test/java/com/crowdin/platform/TranslationDataRepositoryTest.kt @@ -7,9 +7,8 @@ import com.crowdin.platform.data.model.File import com.crowdin.platform.data.model.FileData import com.crowdin.platform.data.model.FileResponse import com.crowdin.platform.data.model.LanguageData -import com.crowdin.platform.data.model.LanguageInfo -import com.crowdin.platform.data.model.LanguageInfoData -import com.crowdin.platform.data.model.LanguagesInfo +import com.crowdin.platform.data.model.LanguageDetails +import com.crowdin.platform.data.model.SupportedLanguages import com.crowdin.platform.data.model.Translation import com.crowdin.platform.data.model.TranslationResponse import com.crowdin.platform.data.parser.Reader @@ -135,12 +134,7 @@ class TranslationDataRepositoryTest { verify(mockReader).parseInput(any()) } - private fun givenSupportedLanguages(): LanguagesInfo { - val languageInfo = LanguageInfo("en", "name", "qq", "www", "en-US", "en-rUS") - return LanguagesInfo( - mutableListOf(LanguageInfoData(languageInfo)), - ) - } + private fun givenSupportedLanguages(): SupportedLanguages = mapOf("en" to LanguageDetails("English", "en-US")) private fun givenMockDistributionData() { val projectData = diff --git a/example/src/main/java/com/crowdin/platform/example/SettingsFragment.kt b/example/src/main/java/com/crowdin/platform/example/SettingsFragment.kt index 1cb3ba9..c2394fe 100644 --- a/example/src/main/java/com/crowdin/platform/example/SettingsFragment.kt +++ b/example/src/main/java/com/crowdin/platform/example/SettingsFragment.kt @@ -53,9 +53,11 @@ class SettingsFragment : Fragment(), OnItemSelectedListener.SpinnerItemListener } // Add supported languages from Crowdin - Crowdin.getManifest()?.languages?.forEach { languageIndex -> - Crowdin.getSupportedLanguages()?.data?.find { it.data.id == languageIndex } - ?.let { languageSet.add(it.data.locale) } + val supportedLanguages = Crowdin.getSupportedLanguages() + Crowdin.getManifest()?.languages?.forEach { languageCode -> + supportedLanguages?.get(languageCode)?.let { languageDetails -> + languageSet.add(languageDetails.locale) + } } // Add current language preference as fallback @@ -88,7 +90,7 @@ class SettingsFragment : Fragment(), OnItemSelectedListener.SpinnerItemListener } private fun updateLanguageDescription(item: String) { - Crowdin.getSupportedLanguages()?.data?.find { it.data.locale == item } - ?.data?.let { languageDescription.text = it.name } + Crowdin.getSupportedLanguages()?.values?.find { it.locale == item } + ?.let { languageDescription.text = it.name } } }