mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-16 23:12:12 +00:00
feat(i18n): extract last deep-cut hardcoded strings to resources
Migrates 13 additional user-facing English literals found across 7 files
in a follow-up audit. Adds 13 new keys (EN + FR) and replaces the inline
literals with `getString(...)` / `runBlocking { getString(...) }`
depending on context.
- RuntimeFormat: episode / runtime hours+minutes template (`%dh %dm`,
`%dh`, `%dm` → resource-backed; FR uses `%d h %d min`).
- SearchRepository: `require {}` empty-result message now hits
`search_error_no_results_for_catalog` instead of the inline literal.
- PluginRepository: `already installed`, install-failed, refresh-failed
fallback messages.
- PluginManifestParser: three `require {}` validation messages (name,
version, providers).
- AddonManifestParser: `Manifest missing "<name>"` exception now uses
`addons_manifest_missing_field`.
- PlayerEngine.ios: `MPV not available — Please rebuild the app` →
`player_error_mpv_unavailable`.
- YoutubeChunkedDataSourceFactory: the developer-facing `No DataSpec`
exception is replaced by a user-friendly `Unable to play this stream.`
message that's safe to render through `onPlayerError.localizedMessage`.
FR translations follow existing conventions (tutoiement, curly apostrophes,
NBSP inside `« »`, internal branch names kept out of user-facing copy).
This commit is contained in:
parent
46a82dce9a
commit
eebc2cd452
9 changed files with 84 additions and 14 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1192,4 +1192,17 @@
|
|||
<string name="unit_bytes_kb">Ko</string>
|
||||
<string name="unit_bytes_mb">Mo</string>
|
||||
<string name="unit_bytes_gb">Go</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 d’installer le dépôt de plugin</string>
|
||||
<string name="plugins_repository_refresh_failed">Impossible d’actualiser 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 l’app.</string>
|
||||
<string name="player_error_unable_to_play_stream">Impossible de lire ce flux.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1272,4 +1272,17 @@
|
|||
<string name="unit_bytes_kb">KB</string>
|
||||
<string name="unit_bytes_mb">MB</string>
|
||||
<string name="unit_bytes_gb">GB</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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -327,7 +327,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()}",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,6 +14,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"
|
||||
|
||||
|
|
@ -44,7 +47,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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue