merge: PR #1029 feat(i18n) extract last deep-cut hardcoded strings

Resolved conflicts in values/strings.xml and values-fr/strings.xml
by concatenating both blocks (no duplicate keys).
This commit is contained in:
Stéphane 2026-05-24 09:41:51 +02:00
commit fd71323cb5
9 changed files with 84 additions and 14 deletions

View file

@ -8,6 +8,10 @@ import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DataSpec
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.datasource.TransferListener
import kotlinx.coroutines.runBlocking
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.player_error_unable_to_play_stream
import org.jetbrains.compose.resources.getString
/**
* A DataSource.Factory that wraps DefaultHttpDataSource and appends YouTube's
@ -75,7 +79,9 @@ class YoutubeChunkedDataSourceFactory(
}
private fun openNextChunk(): Long {
val spec = originalDataSpec ?: throw IllegalStateException("No DataSpec")
val spec = originalDataSpec ?: throw IllegalStateException(
runBlocking { getString(Res.string.player_error_unable_to_play_stream) },
)
val end = if (totalContentLength != C.LENGTH_UNSET.toLong()) {
minOf(currentChunkStart + chunkSize - 1, currentChunkStart + totalContentLength - 1)
} else {

View file

@ -1527,4 +1527,17 @@
<string name="downloads_error_open_partial_file_failed">Impossible douvrir le fichier de téléchargement partiel</string>
<string name="downloads_error_partial_file_not_open">Le fichier de téléchargement partiel nest pas ouvert</string>
<string name="downloads_error_write_partial_file_failed">Impossible décrire dans le fichier de téléchargement partiel</string>
<string name="details_runtime_hours_minutes">%1$d h %2$d min</string>
<string name="details_runtime_hours_only">%1$d h</string>
<string name="details_runtime_minutes_only">%1$d min</string>
<string name="search_error_no_results_for_catalog">Aucun résultat de recherche pour %1$s.</string>
<string name="plugins_repository_already_installed">Ce dépôt de plugin est déjà installé.</string>
<string name="plugins_repository_install_failed">Impossible dinstaller le dépôt de plugin</string>
<string name="plugins_repository_refresh_failed">Impossible dactualiser le dépôt</string>
<string name="plugins_manifest_name_missing">Le nom du manifeste est manquant.</string>
<string name="plugins_manifest_version_missing">La version du manifeste est manquante.</string>
<string name="plugins_manifest_no_providers">Le manifeste ne contient aucun fournisseur.</string>
<string name="addons_manifest_missing_field">Champ « %1$s » manquant dans le manifeste</string>
<string name="player_error_mpv_unavailable">Moteur de lecture MPV indisponible. Reconstruis lapp.</string>
<string name="player_error_unable_to_play_stream">Impossible de lire ce flux.</string>
</resources>

View file

@ -1584,4 +1584,17 @@
<string name="downloads_error_open_partial_file_failed">Failed to open partial download file</string>
<string name="downloads_error_partial_file_not_open">Partial download file is not open</string>
<string name="downloads_error_write_partial_file_failed">Failed to write partial download file</string>
<string name="details_runtime_hours_minutes">%1$dh %2$dm</string>
<string name="details_runtime_hours_only">%1$dh</string>
<string name="details_runtime_minutes_only">%1$dm</string>
<string name="search_error_no_results_for_catalog">No search results returned for %1$s.</string>
<string name="plugins_repository_already_installed">That plugin repository is already installed.</string>
<string name="plugins_repository_install_failed">Unable to install plugin repository</string>
<string name="plugins_repository_refresh_failed">Unable to refresh repository</string>
<string name="plugins_manifest_name_missing">Manifest name is missing.</string>
<string name="plugins_manifest_version_missing">Manifest version is missing.</string>
<string name="plugins_manifest_no_providers">Manifest has no providers.</string>
<string name="addons_manifest_missing_field">Manifest missing \"%1$s\"</string>
<string name="player_error_mpv_unavailable">MPV player engine not available. Please rebuild the app.</string>
<string name="player_error_unable_to_play_stream">Unable to play this stream.</string>
</resources>

View file

@ -1,5 +1,6 @@
package com.nuvio.app.features.addons
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
@ -8,6 +9,9 @@ import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.addons_manifest_missing_field
import org.jetbrains.compose.resources.getString
internal object AddonManifestParser {
private val json = Json {
@ -92,7 +96,9 @@ internal object AddonManifestParser {
private fun JsonObject.requiredString(name: String): String =
optionalString(name)?.takeIf { it.isNotBlank() }
?: throw IllegalArgumentException("Manifest missing \"$name\"")
?: throw IllegalArgumentException(
runBlocking { getString(Res.string.addons_manifest_missing_field, name) },
)
private fun JsonObject.optionalString(name: String): String? =
this[name]?.jsonPrimitive?.contentOrNull

View file

@ -1,5 +1,12 @@
package com.nuvio.app.features.details
import kotlinx.coroutines.runBlocking
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.details_runtime_hours_minutes
import nuvio.composeapp.generated.resources.details_runtime_hours_only
import nuvio.composeapp.generated.resources.details_runtime_minutes_only
import org.jetbrains.compose.resources.getString
private val hourTokenRegex = Regex("""(?i)(\d+)\s*h(?:ours?)?""")
private val minuteTokenRegex = Regex("""(?i)(\d+)\s*m(?:in(?:ute)?s?)?""")
private val hourMinuteColonRegex = Regex("""^\s*(\d+)\s*:\s*(\d{1,2})\s*$""")
@ -16,10 +23,12 @@ internal fun formatRuntimeFromMinutes(totalMinutes: Int): String {
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return when {
hours > 0 && minutes > 0 -> "${hours}h ${minutes}m"
hours > 0 -> "${hours}h"
else -> "${minutes}m"
return runBlocking {
when {
hours > 0 && minutes > 0 -> getString(Res.string.details_runtime_hours_minutes, hours, minutes)
hours > 0 -> getString(Res.string.details_runtime_hours_only, hours)
else -> getString(Res.string.details_runtime_minutes_only, minutes)
}
}
}

View file

@ -369,7 +369,9 @@ object SearchRepository {
search = query,
).withUnreleasedFilter()
val items = page.items
require(items.isNotEmpty()) { "No search results returned for $catalogName." }
require(items.isNotEmpty()) {
getString(Res.string.search_error_no_results_for_catalog, catalogName)
}
return HomeCatalogSection(
key = "${manifest.id}:search:$type:$catalogId:${query.lowercase()}",

View file

@ -1,6 +1,12 @@
package com.nuvio.app.features.plugins
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.plugins_manifest_name_missing
import nuvio.composeapp.generated.resources.plugins_manifest_no_providers
import nuvio.composeapp.generated.resources.plugins_manifest_version_missing
import org.jetbrains.compose.resources.getString
internal object PluginManifestParser {
private val json = Json {
@ -9,9 +15,15 @@ internal object PluginManifestParser {
fun parse(payload: String): PluginManifest {
val manifest = json.decodeFromString<PluginManifest>(payload)
require(manifest.name.isNotBlank()) { "Manifest name is missing." }
require(manifest.version.isNotBlank()) { "Manifest version is missing." }
require(manifest.scrapers.isNotEmpty()) { "Manifest has no providers." }
require(manifest.name.isNotBlank()) {
runBlocking { getString(Res.string.plugins_manifest_name_missing) }
}
require(manifest.version.isNotBlank()) {
runBlocking { getString(Res.string.plugins_manifest_version_missing) }
}
require(manifest.scrapers.isNotEmpty()) {
runBlocking { getString(Res.string.plugins_manifest_no_providers) }
}
return manifest
}
}

View file

@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -25,6 +26,11 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.put
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.plugins_repository_already_installed
import nuvio.composeapp.generated.resources.plugins_repository_install_failed
import nuvio.composeapp.generated.resources.plugins_repository_refresh_failed
import org.jetbrains.compose.resources.getString
@Serializable
private data class PluginRow(
@ -149,7 +155,7 @@ actual object PluginRepository {
}
if (_uiState.value.repositories.any { it.manifestUrl == manifestUrl }) {
return AddPluginRepositoryResult.Error("That plugin repository is already installed.")
return AddPluginRepositoryResult.Error(getString(Res.string.plugins_repository_already_installed))
}
return try {
@ -168,7 +174,7 @@ actual object PluginRepository {
pushToServer()
AddPluginRepositoryResult.Success(repo)
} catch (error: Throwable) {
AddPluginRepositoryResult.Error(error.message ?: "Unable to install plugin repository")
AddPluginRepositoryResult.Error(error.message ?: getString(Res.string.plugins_repository_install_failed))
}
}
@ -232,7 +238,7 @@ actual object PluginRepository {
if (existing.manifestUrl == manifestUrl) {
existing.copy(
isRefreshing = false,
errorMessage = error.message ?: "Unable to refresh repository",
errorMessage = error.message ?: runBlocking { getString(Res.string.plugins_repository_refresh_failed) },
)
} else {
existing

View file

@ -16,6 +16,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.player_error_mpv_unavailable
import org.jetbrains.compose.resources.getString
private const val TAG = "NuvioiOSPlayer"
@ -49,7 +52,7 @@ actual fun PlatformPlayerSurface(
if (bridge == null) {
LaunchedEffect(Unit) {
latestOnError.value("MPV player engine not available. Please rebuild the app.")
latestOnError.value(getString(Res.string.player_error_mpv_unavailable))
}
return
}