diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 64283fe6..ae3e3fd8 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -194,6 +194,8 @@ dependencies { // FrameExtractor: seek-bar frames for HLS (preview/HlsFramePreview.kt). implementation("androidx.media3:media3-inspector:$media3") implementation("androidx.media3:media3-datasource-okhttp:$media3") + // MediaSessionCompat for the player: headset and Bluetooth buttons. + implementation("androidx.media:media:1.7.0") // CloudStream provider runtime (Android-only feature). The `library` module // carries MainAPI/APIHolder/`app` HTTP client/extractors/BasePlugin so .cs3 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8cb51c74..04266d17 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -67,7 +67,8 @@ android:hardwareAccelerated="true" android:supportsPictureInPicture="true" android:resizeableActivity="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:enableOnBackInvokedCallback="true"> + diff --git a/android/app/src/main/kotlin/com/soplay/sozo/AudioFocusHelper.kt b/android/app/src/main/kotlin/com/soplay/sozo/AudioFocusHelper.kt new file mode 100644 index 00000000..46fba4cc --- /dev/null +++ b/android/app/src/main/kotlin/com/soplay/sozo/AudioFocusHelper.kt @@ -0,0 +1,96 @@ +package com.soplay.sozo + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.media.AudioAttributes +import android.media.AudioFocusRequest +import android.media.AudioManager +import android.os.Build + +/** + * Audio focus and ACTION_AUDIO_BECOMING_NOISY for the players that do not + * handle them themselves (libmpv). Events go to [onEvent] as the wire names + * the Dart AudioFocusPolicy reads: gain, loss, loss_transient, duck, noisy. + */ +class AudioFocusHelper( + private val context: Context, + private val onEvent: (String) -> Unit, +) { + private val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var hasFocus = false + private var request: AudioFocusRequest? = null + private var noisyReceiver: BroadcastReceiver? = null + + private val listener = AudioManager.OnAudioFocusChangeListener { change -> + when (change) { + AudioManager.AUDIOFOCUS_GAIN -> { + hasFocus = true + onEvent("gain") + } + AudioManager.AUDIOFOCUS_LOSS -> { + hasFocus = false + onEvent("loss") + } + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> onEvent("loss_transient") + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> onEvent("duck") + } + } + + fun request(): Boolean { + if (hasFocus) return true + val granted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val req = request ?: AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .build() + ) + .setOnAudioFocusChangeListener(listener) + .build() + .also { request = it } + audio.requestAudioFocus(req) + } else { + @Suppress("DEPRECATION") + audio.requestAudioFocus(listener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + } + hasFocus = granted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED + return hasFocus + } + + fun abandon() { + if (!hasFocus) return + hasFocus = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + request?.let { audio.abandonAudioFocusRequest(it) } + } else { + @Suppress("DEPRECATION") + audio.abandonAudioFocus(listener) + } + } + + fun watchNoisy() { + if (noisyReceiver != null) return + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) onEvent("noisy") + } + } + val filter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) + } else { + @Suppress("UnspecifiedRegisterReceiverFlag") + context.registerReceiver(receiver, filter) + } + noisyReceiver = receiver + } + + fun release() { + abandon() + noisyReceiver?.let { runCatching { context.unregisterReceiver(it) } } + noisyReceiver = null + } +} diff --git a/android/app/src/main/kotlin/com/soplay/sozo/MainActivity.kt b/android/app/src/main/kotlin/com/soplay/sozo/MainActivity.kt index a3161af5..e65b338f 100644 --- a/android/app/src/main/kotlin/com/soplay/sozo/MainActivity.kt +++ b/android/app/src/main/kotlin/com/soplay/sozo/MainActivity.kt @@ -11,12 +11,15 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.res.Configuration +import android.graphics.Rect import android.net.Uri import android.media.AudioManager import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle import android.provider.Settings +import android.util.Rational +import androidx.lifecycle.Lifecycle import androidx.annotation.RequiresApi import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -66,6 +69,20 @@ class MainActivity : FlutterFragmentActivity() { private var glassPower: GlassPower? = null private var deeplinkSettingsChannel: MethodChannel? = null private var pipReceiver: BroadcastReceiver? = null + + private val playerSystemChannelName = "soplay/player_system" + private var playerSystemChannel: MethodChannel? = null + private var audioFocus: AudioFocusHelper? = null + private var mediaSession: PlayerMediaSession? = null + + // Auto-enter PiP on Home while playing. On 12+ the system does it from + // the params; before that, onUserLeaveHint does. + private var autoPip = false + private var pipAspect = Rational(16, 9) + private var pipSourceRect: Rect? = null + private var pipActions: List = emptyList() + private var pipLabels: Map = emptyMap() + private var pipExitPending = false private var notificationPermissionResult: MethodChannel.Result? = null // CloudStream plugin host (Android-only feature). Lazy so the runtime only @@ -224,11 +241,24 @@ class MainActivity : FlutterFragmentActivity() { val isPlaying = call.argument("isPlaying") ?: true val hasPrev = call.argument("hasPrev") ?: false val hasNext = call.argument("hasNext") ?: false + val seekSeconds = call.argument("seekSeconds")?.toInt() ?: 10 + call.argument>("labels")?.let { pipLabels = it } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - applyPipActions(isPlaying, hasPrev, hasNext) + applyPipActions(isPlaying, hasPrev, hasNext, seekSeconds) } result.success(true) } + "setAutoPip" -> { + autoPip = call.argument("enabled") ?: false + val num = call.argument("numerator")?.toInt() ?: 16 + val den = call.argument("denominator")?.toInt() ?: 9 + if (num > 0 && den > 0) pipAspect = Rational(num, den) + pipSourceRect = call.argument>("sourceRect") + ?.takeIf { it.size == 4 } + ?.let { Rect(it[0].toInt(), it[1].toInt(), it[2].toInt(), it[3].toInt()) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) applyPipParams() + result.success(true) + } else -> result.notImplemented() } } @@ -456,6 +486,62 @@ class MainActivity : FlutterFragmentActivity() { } } + playerSystemChannel = MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + playerSystemChannelName + ) + playerSystemChannel?.setMethodCallHandler { call, result -> + when (call.method) { + "attach" -> { + val focus = audioFocus ?: AudioFocusHelper(applicationContext) { event -> + runOnUiThread { playerSystemChannel?.invokeMethod("audioFocus", event) } + }.also { audioFocus = it } + focus.watchNoisy() + val session = mediaSession ?: PlayerMediaSession(this) { action, pos -> + runOnUiThread { + playerSystemChannel?.invokeMethod( + "mediaCommand", + mapOf("action" to action, "positionMs" to pos) + ) + } + }.also { mediaSession = it } + session.start() + result.success(true) + } + "detach" -> { + audioFocus?.release() + mediaSession?.stop() + result.success(true) + } + "requestAudioFocus" -> result.success(audioFocus?.request() ?: true) + "abandonAudioFocus" -> { + audioFocus?.abandon() + result.success(true) + } + "setMetadata" -> { + mediaSession?.setMetadata( + call.argument("title") ?: "", + call.argument("subtitle"), + call.argument("durationMs")?.toLong() ?: 0L, + call.argument("artworkUrl"), + ) + result.success(true) + } + "setPlaybackState" -> { + mediaSession?.setState( + call.argument("playing") ?: false, + call.argument("positionMs")?.toLong() ?: 0L, + call.argument("speed")?.toFloat() ?: 1f, + call.argument("hasPrev") ?: false, + call.argument("hasNext") ?: false, + call.argument("seekable") ?: true, + ) + result.success(true) + } + else -> result.notImplemented() + } + } + deeplinkSettingsChannel = MethodChannel( flutterEngine.dartExecutor.binaryMessenger, deeplinkSettingsChannelName @@ -1226,14 +1312,19 @@ class MainActivity : FlutterFragmentActivity() { audio.setStreamVolume(AudioManager.STREAM_MUSIC, level, 0) } + // The window override is the truth once the player has set one. Before + // that, the system level is only a guess: its range is 0..255 on most + // phones but not all, and under auto-brightness it is stale. private fun getWindowBrightness(): Double { val windowValue = window.attributes.screenBrightness if (windowValue >= 0f) return windowValue.toDouble().coerceIn(0.0, 1.0) return try { - Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS) - .toDouble() - .div(255.0) - .coerceIn(0.0, 1.0) + val auto = Settings.System.getInt( + contentResolver, + Settings.System.SCREEN_BRIGHTNESS_MODE + ) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC + val raw = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS) + if (auto || raw !in 0..255) 0.5 else (raw / 255.0).coerceIn(0.05, 1.0) } catch (_: Exception) { 0.5 } @@ -1348,74 +1439,133 @@ class MainActivity : FlutterFragmentActivity() { private fun applyPipActions( isPlaying: Boolean, hasPrev: Boolean, - hasNext: Boolean + hasNext: Boolean, + seekSeconds: Int ) { + fun label(key: String, fallback: String) = pipLabels[key]?.takeIf { it.isNotBlank() } ?: fallback val actions = mutableListOf() - - if (hasPrev) { - actions.add( - makeAction( - android.R.drawable.ic_media_previous, - "Previous", - "Previous episode", - ACTION_PREV, - REQ_PREV - ) - ) + // Most windows take three; the episode buttons only when there is room. + val room = maxNumPictureInPictureActions + val showPrev = hasPrev && room >= 5 + val showNext = hasNext && room >= 4 + if (showPrev) { + val t = label("prev", "Previous") + actions.add(makeAction(R.drawable.ic_pip_prev, t, t, ACTION_PREV, REQ_PREV)) } + val rewind = label("rewind", "Rewind ${seekSeconds}s") actions.add( - makeAction( - android.R.drawable.ic_media_rew, - "Rewind 10", - "Rewind 10 seconds", - ACTION_REWIND, - REQ_REWIND - ) + makeAction(replayIcon(seekSeconds), rewind, rewind, ACTION_REWIND, REQ_REWIND) ) + val playPause = if (isPlaying) label("pause", "Pause") else label("play", "Play") actions.add( makeAction( - if (isPlaying) - android.R.drawable.ic_media_pause - else - android.R.drawable.ic_media_play, - if (isPlaying) "Pause" else "Play", - if (isPlaying) "Pause" else "Play", + if (isPlaying) R.drawable.ic_pip_pause else R.drawable.ic_pip_play, + playPause, + playPause, ACTION_PLAY_PAUSE, REQ_PLAY_PAUSE ) ) + val forward = label("forward", "Forward ${seekSeconds}s") actions.add( - makeAction( - android.R.drawable.ic_media_ff, - "Forward 10", - "Forward 10 seconds", - ACTION_FORWARD, - REQ_FORWARD - ) + makeAction(forwardIcon(seekSeconds), forward, forward, ACTION_FORWARD, REQ_FORWARD) ) - if (hasNext) { - actions.add( - makeAction( - android.R.drawable.ic_media_next, - "Next", - "Next episode", - ACTION_NEXT, - REQ_NEXT - ) - ) + if (showNext) { + val t = label("next", "Next") + actions.add(makeAction(R.drawable.ic_pip_next, t, t, ACTION_NEXT, REQ_NEXT)) } + pipActions = actions + applyPipParams() + } + + private fun replayIcon(seconds: Int) = when (seconds) { + 5 -> R.drawable.ic_pip_replay_5 + 10 -> R.drawable.ic_pip_replay_10 + 30 -> R.drawable.ic_pip_replay_30 + else -> R.drawable.ic_pip_replay + } - val params = PictureInPictureParams.Builder() - .setActions(actions) - .build() + private fun forwardIcon(seconds: Int) = when (seconds) { + 5 -> R.drawable.ic_pip_forward_5 + 10 -> R.drawable.ic_pip_forward_10 + 30 -> R.drawable.ic_pip_forward_30 + else -> R.drawable.ic_pip_forward + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun buildPipParams(): PictureInPictureParams { + val builder = PictureInPictureParams.Builder() + .setActions(pipActions) + .setAspectRatio(pipAspect) + pipSourceRect?.let { builder.setSourceRectHint(it) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setAutoEnterEnabled(autoPip) + builder.setSeamlessResizeEnabled(true) + } + return builder.build() + } + @RequiresApi(Build.VERSION_CODES.O) + private fun applyPipParams() { try { - setPictureInPictureParams(params) + setPictureInPictureParams(buildPipParams()) } catch (_: Exception) { // Activity may not be in a state to receive PiP params yet } } + override fun onUserLeaveHint() { + super.onUserLeaveHint() + if (!autoPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) return + if (isInPictureInPictureMode) return + try { + enterPictureInPictureMode(buildPipParams()) + } catch (_: Exception) { + } + } + + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration + ) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + if (isInPictureInPictureMode) { + pipExitPending = false + sendPipMode(inPip = true, dismissed = false) + return + } + // Closed with its X, the activity is already stopped (12+) or about + // to be (older: onStop follows). Expanded, it resumes instead. + val stopped = !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) + if (stopped || isFinishing) { + sendPipMode(inPip = false, dismissed = true) + } else { + pipExitPending = true + sendPipMode(inPip = false, dismissed = false) + } + } + + override fun onResume() { + super.onResume() + pipExitPending = false + } + + override fun onStop() { + super.onStop() + if (pipExitPending) { + pipExitPending = false + sendPipMode(inPip = false, dismissed = true) + } + } + + private fun sendPipMode(inPip: Boolean, dismissed: Boolean) { + methodChannel?.invokeMethod( + "onPipModeChanged", + mapOf("inPip" to inPip, "dismissed" to dismissed) + ) + } + @RequiresApi(Build.VERSION_CODES.O) private fun makeAction( iconRes: Int, @@ -1494,6 +1644,10 @@ class MainActivity : FlutterFragmentActivity() { // with one secure decoder that is the next playback failing to start. drmPlayerHost?.dispose() drmPlayerHost = null + audioFocus?.release() + audioFocus = null + mediaSession?.dispose() + mediaSession = null try { bridgeServer?.stop() } catch (_: Throwable) {} bridgeServer = null pipReceiver?.let { diff --git a/android/app/src/main/kotlin/com/soplay/sozo/PlayerMediaSession.kt b/android/app/src/main/kotlin/com/soplay/sozo/PlayerMediaSession.kt new file mode 100644 index 00000000..7cd91de2 --- /dev/null +++ b/android/app/src/main/kotlin/com/soplay/sozo/PlayerMediaSession.kt @@ -0,0 +1,140 @@ +package com.soplay.sozo + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.SystemClock +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.Executors + +/** + * The player's media session, live while the player page is open: headset + * and Bluetooth buttons reach the player, and the system knows what is + * playing. Commands go to [onCommand] as (action, positionMs). + */ +class PlayerMediaSession( + private val context: Context, + private val onCommand: (String, Long?) -> Unit, +) { + private var session: MediaSessionCompat? = null + private val artLoader = Executors.newSingleThreadExecutor() + private var artUrl: String? = null + private var art: Bitmap? = null + private var metadata = MediaMetadataCompat.Builder() + + private val callback = object : MediaSessionCompat.Callback() { + override fun onPlay() = onCommand("play", null) + override fun onPause() = onCommand("pause", null) + override fun onStop() = onCommand("pause", null) + override fun onSeekTo(pos: Long) = onCommand("seek", pos) + override fun onFastForward() = onCommand("forward", null) + override fun onRewind() = onCommand("rewind", null) + override fun onSkipToNext() = onCommand("next", null) + override fun onSkipToPrevious() = onCommand("prev", null) + } + + fun start() { + if (session != null) return + session = MediaSessionCompat(context, "SozoPlayer").apply { + setCallback(callback) + isActive = true + } + } + + fun setMetadata(title: String, subtitle: String?, durationMs: Long, artworkUrl: String?) { + val s = session ?: return + metadata = MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) + .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title) + .apply { + if (!subtitle.isNullOrBlank()) { + putString(MediaMetadataCompat.METADATA_KEY_ARTIST, subtitle) + putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, subtitle) + } + if (durationMs > 0) putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs) + } + if (artworkUrl != artUrl) { + artUrl = artworkUrl + art = null + if (!artworkUrl.isNullOrBlank()) loadArt(artworkUrl) + } + art?.let { metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, it) } + s.setMetadata(metadata.build()) + } + + fun setState(playing: Boolean, positionMs: Long, speed: Float, hasPrev: Boolean, hasNext: Boolean, seekable: Boolean) { + val s = session ?: return + var actions = PlaybackStateCompat.ACTION_PLAY or + PlaybackStateCompat.ACTION_PAUSE or + PlaybackStateCompat.ACTION_PLAY_PAUSE or + PlaybackStateCompat.ACTION_STOP + if (seekable) { + actions = actions or PlaybackStateCompat.ACTION_SEEK_TO or + PlaybackStateCompat.ACTION_FAST_FORWARD or + PlaybackStateCompat.ACTION_REWIND + } + if (hasPrev) actions = actions or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + if (hasNext) actions = actions or PlaybackStateCompat.ACTION_SKIP_TO_NEXT + s.setPlaybackState( + PlaybackStateCompat.Builder() + .setActions(actions) + .setState( + if (playing) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, + positionMs, + if (playing) speed else 0f, + SystemClock.elapsedRealtime(), + ) + .build() + ) + } + + fun stop() { + artUrl = null + art = null + session?.let { + it.isActive = false + it.setCallback(null) + it.release() + } + session = null + } + + fun dispose() { + stop() + artLoader.shutdownNow() + } + + private fun loadArt(url: String) { + artLoader.execute { + val bitmap = runCatching { fetch(url) }.getOrNull() ?: return@execute + android.os.Handler(android.os.Looper.getMainLooper()).post { + if (url != artUrl) return@post + art = bitmap + session?.setMetadata( + metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap).build() + ) + } + } + } + + private fun fetch(url: String): Bitmap? { + val conn = URL(url).openConnection() as HttpURLConnection + conn.connectTimeout = 8000 + conn.readTimeout = 8000 + return try { + if (conn.responseCode !in 200..299) return null + val bytes = conn.inputStream.use { it.readBytes() } + val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds) + var sample = 1 + while (bounds.outWidth / (sample * 2) >= 512 && bounds.outHeight / (sample * 2) >= 512) sample *= 2 + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, BitmapFactory.Options().apply { inSampleSize = sample }) + } finally { + conn.disconnect() + } + } +} diff --git a/android/app/src/main/kotlin/com/soplay/sozo/drm/DrmPlayerHost.kt b/android/app/src/main/kotlin/com/soplay/sozo/drm/DrmPlayerHost.kt index f6cfcf10..a2f4f1c5 100644 --- a/android/app/src/main/kotlin/com/soplay/sozo/drm/DrmPlayerHost.kt +++ b/android/app/src/main/kotlin/com/soplay/sozo/drm/DrmPlayerHost.kt @@ -5,6 +5,7 @@ import android.os.Handler import android.os.Looper import android.util.Log import android.view.Surface +import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackException @@ -221,6 +222,14 @@ class DrmPlayerHost( .setDrmSessionManagerProvider { drmManager }, ) .build() + player.setAudioAttributes( + AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) + .build(), + /* handleAudioFocus = */ true, + ) + player.setHandleAudioBecomingNoisy(true) val entry = textures.createSurfaceTexture() val surface = Surface(entry.surfaceTexture()) diff --git a/android/app/src/main/res/drawable/ic_launcher_monochrome.xml b/android/app/src/main/res/drawable/ic_launcher_monochrome.xml new file mode 100644 index 00000000..84f95d26 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_forward.xml b/android/app/src/main/res/drawable/ic_pip_forward.xml new file mode 100644 index 00000000..66eea3b3 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_forward.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_forward_10.xml b/android/app/src/main/res/drawable/ic_pip_forward_10.xml new file mode 100644 index 00000000..099e79df --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_forward_10.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_forward_30.xml b/android/app/src/main/res/drawable/ic_pip_forward_30.xml new file mode 100644 index 00000000..942ab9f4 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_forward_30.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_forward_5.xml b/android/app/src/main/res/drawable/ic_pip_forward_5.xml new file mode 100644 index 00000000..f7ca4113 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_forward_5.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_next.xml b/android/app/src/main/res/drawable/ic_pip_next.xml new file mode 100644 index 00000000..939365a1 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_next.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_pause.xml b/android/app/src/main/res/drawable/ic_pip_pause.xml new file mode 100644 index 00000000..c984ad96 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_pause.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_play.xml b/android/app/src/main/res/drawable/ic_pip_play.xml new file mode 100644 index 00000000..d8efce31 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_play.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_prev.xml b/android/app/src/main/res/drawable/ic_pip_prev.xml new file mode 100644 index 00000000..1087fb3d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_prev.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_replay.xml b/android/app/src/main/res/drawable/ic_pip_replay.xml new file mode 100644 index 00000000..43dce0c0 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_replay.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_replay_10.xml b/android/app/src/main/res/drawable/ic_pip_replay_10.xml new file mode 100644 index 00000000..d642263c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_replay_10.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_replay_30.xml b/android/app/src/main/res/drawable/ic_pip_replay_30.xml new file mode 100644 index 00000000..09d66c5f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_replay_30.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_pip_replay_5.xml b/android/app/src/main/res/drawable/ic_pip_replay_5.xml new file mode 100644 index 00000000..ea524253 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pip_replay_5.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 79d695e1..19e74afc 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,4 +1,5 @@ - \ No newline at end of file + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 79d695e1..19e74afc 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,4 +1,5 @@ - \ No newline at end of file + + diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 2469a828..a37719b0 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -34,7 +34,9 @@ "remove": "إزالة", "show": "إظهار", "hide": "إخفاء", - "installed": "مثبّت" + "installed": "مثبّت", + "load_more_failed": "تعذّر تحميل المزيد", + "go_home": "العودة إلى الرئيسية" }, "auth": { "login": "تسجيل الدخول", @@ -306,7 +308,11 @@ "no_source": "لا يوجد مصدر لهذه الحلقة.", "empty_url": "لم يُرجِع هذا المصدر عنوان فيديو.", "engine_unavailable": "لم يبدأ المشغّل. أعد تشغيل التطبيق بالكامل.", - "unknown": "تعذّر تحميل الفيديو." + "unknown": "تعذّر تحميل الفيديو.", + "not_found": "لم يعد هذا الفيديو موجودًا على هذا الخادم. جرّب خادمًا آخر.", + "server_error": "يواجه خادم الفيديو مشكلة. حاول مجددًا بعد قليل أو اختر خادمًا آخر.", + "resolve_failed": "تعذّر الحصول على رابط الفيديو من هذا المصدر.", + "details": "التفاصيل" }, "play": "تشغيل", "pause": "إيقاف مؤقت", @@ -570,6 +576,8 @@ "subtitle_n": "ترجمة {}", "subtitle_in_video": "ضمن الفيديو", "rewind": "ترجيع", + "rewind_by": "ترجيع {} ثانية", + "forward_by": "تقديم {} ثانية", "mini_restore": "العودة إلى النافذة الكاملة", "pip_unavailable": "تعذّر تشغيل صورة داخل صورة لهذا الفيديو.", "scrub_release_cancel": "اترك للإلغاء", @@ -585,7 +593,18 @@ "reaction_text_hint": "اكتب شيئاً قصيراً…", "reaction_wait": "انتظر بضع ثوانٍ قبل التفاعل مجدداً", "reaction_limit": "وصلت إلى حد التفاعلات لهذه الحلقة", - "reaction_failed": "تعذّر إرسال التفاعل" + "reaction_failed": "تعذّر إرسال التفاعل", + "native_offer_title": "هل تريد تجربة المشغّل الجديد؟", + "native_offer_body": "أزرار وقوائم Liquid Glass من Apple. يمكنك تغيير ذلك في أي وقت من الإعدادات ← المشغّل.", + "native_offer_try": "جرّبه", + "native_offer_not_now": "ليس الآن", + "trying_again": "إعادة المحاولة (المحاولة {})", + "try_mpv": "جرّب باستخدام محرك mpv", + "ok_to_skip": "اضغط OK للتخطي", + "quality_lowered": "تم خفض الجودة", + "undo": "تراجع", + "crash_log": "سجل الأعطال", + "crash_log_empty": "لم تُسجَّل أي أعطال" }, "profile": { "title": "حسابي", @@ -677,6 +696,15 @@ "player_engine_section": "محرّك التشغيل", "ask_engine_on_play": "اسأل في كل مرة", "ask_engine_on_play_desc": "اختر المحرّك عند بدء كل فيديو", + "controls_style": "نمط أزرار المشغّل", + "controls_style_desc": "يُطبَّق في المرة القادمة التي تفتح فيها المشغّل.", + "controls_style_native": "أصلي (iOS 26)", + "controls_style_native_desc": "أزرار وقوائم ونوافذ Liquid Glass الخاصة بالهاتف", + "controls_style_system": "زجاج النظام", + "controls_style_system_desc": "أزرار التطبيق على زجاج Liquid Glass الخاص بالهاتف", + "controls_style_classic": "زجاج كلاسيكي", + "controls_style_classic_desc": "زجاج التطبيق الخاص، كما في الإصدارات السابقة", + "controls_style_needs_ios26": "يتطلب iOS 26 أو أحدث", "playback_section": "التشغيل", "default_speed": "السرعة الافتراضية", "default_speed_desc": "السرعة التي يبدأ بها كل فيديو", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "اسحب لأعلى وأسفل على الجانب الأيسر", "volume_gesture": "سحب الصوت", "volume_gesture_desc": "اسحب لأعلى وأسفل على الجانب الأيمن", + "auto_pip": "صورة داخل صورة تلقائيًا", + "auto_pip_desc": "عند الضغط على زر الشاشة الرئيسية أثناء تشغيل فيديو، يستمر في نافذة صغيرة", "seconds_short": "{}ث", "subtitle_style_note": "تنطبق هذه على كل فيديو. أمّا توقيت الترجمة فيبقى لكل حلقة ويُضبط داخل المشغّل.", "searching_all_sources": "البحث في كل المصادر", @@ -2060,7 +2090,8 @@ "title": "قوائمي", "remove": "إزالة", "empty_title": "لا شيء محفوظ بعد", - "empty_subtitle": "ستظهر هنا الأعمال التي تحفظها." + "empty_subtitle": "ستظهر هنا الأعمال التي تحفظها.", + "load_failed": "تعذّر تحميل هذه القائمة." }, "torrent": { "title": "التورنت", @@ -2914,6 +2945,8 @@ "priming_title": "لا تفوّت أي حلقة جديدة", "priming_body": "يخبرك Sozo فور صدور حلقة أو فصل جديد لما تتابعه — ولا شيء لم تطلبه.", "priming_body_title": "اعرف فور صدور حلقة جديدة من «{}» — ولا شيء لم تطلبه.", + "priming_download_title": "اعرف متى يكتمل التنزيل", + "priming_download_body": "يعرض Sozo تقدّم التنزيل في الإشعارات ويخبرك عندما يصبح جاهزًا للمشاهدة دون اتصال. ويمكنه أيضًا إعلامك بالحلقات الجديدة لما تتابعه.", "priming_point_releases": "حلقات جديدة مع الملصق", "priming_point_quiet": "ساعات الهدوء", "priming_point_control": "كتم أي عمل", @@ -3142,7 +3175,8 @@ "error_attachment_count": "5 لقطات شاشة كحد أقصى في الرسالة.", "suggest": "تتكرر المشكلة؟ راسل الدعم", "error_offline": "أنت غير متصل أو انقطع الاتصال. تحقق من الإنترنت ثم حاول مرة أخرى.", - "not_sent": "لم يُرسل: {}" + "not_sent": "لم يُرسل: {}", + "diag_crash_log": "عرض سجل الأعطال" }, "zangetsu": { "title": "مصادر Zangetsu", diff --git a/assets/translations/ckb.json b/assets/translations/ckb.json index 0bd7bf68..5073368a 100644 --- a/assets/translations/ckb.json +++ b/assets/translations/ckb.json @@ -34,7 +34,9 @@ "remove": "لابردن", "show": "پیشاندان", "hide": "شاردنەوە", - "installed": "دامەزراوە" + "installed": "دامەزراوە", + "load_more_failed": "نەتوانرا زیاتر باربکرێت", + "go_home": "بڕۆ بۆ سەرەکی" }, "auth": { "login": "چوونەژوورەوە", @@ -306,7 +308,11 @@ "no_source": "هیچ سەرچاوەیەک بۆ ئەم ئەڵقەیە نییە.", "empty_url": "ئەم سەرچاوەیە هیچ ناونیشانێکی ڤیدیۆی نەگەڕاندەوە.", "engine_unavailable": "لێدەرەکە دەستی پێنەکرد. تکایە ئەپەکە بە تەواوی دابخە و دووبارە بیکەرەوە.", - "unknown": "نەتوانرا ڤیدیۆکە بار بکرێت." + "unknown": "نەتوانرا ڤیدیۆکە بار بکرێت.", + "not_found": "ئەم ڤیدیۆیە چیتر لەسەر ئەم سێرڤەرە نییە. سێرڤەرێکی تر تاقی بکەرەوە.", + "server_error": "سێرڤەری ڤیدیۆ کێشەی هەیە. کەمێکی تر دووبارە هەوڵ بدەرەوە یان سێرڤەرێکی تر هەڵبژێرە.", + "resolve_failed": "نەتوانرا بەستەری ڤیدیۆ لەم سەرچاوەیە وەربگیرێت.", + "details": "وردەکاری" }, "play": "لێدان", "pause": "وەستاندن", @@ -570,6 +576,8 @@ "subtitle_n": "ژێرنووس {}", "subtitle_in_video": "لەناو ڤیدیۆکەدا", "rewind": "گەڕاندنەوە بۆ دواوە", + "rewind_by": "{} چرکە بۆ دواوە", + "forward_by": "{} چرکە بۆ پێشەوە", "mini_restore": "گەڕانەوە بۆ پەنجەرەی تەواو", "pip_unavailable": "وێنە لەناو وێنە نەیتوانی بۆ ئەم ڤیدیۆیە دەستپێبکات.", "scrub_release_cancel": "بەری بدە بۆ هەڵوەشاندنەوە", @@ -585,7 +593,18 @@ "reaction_text_hint": "شتێکی کورت بنووسە…", "reaction_wait": "چەند چرکەیەک چاوەڕێ بکە پێش کاردانەوەی دووبارە", "reaction_limit": "گەیشتیتە سنووری کاردانەوەکان بۆ ئەم ئەڵقەیە", - "reaction_failed": "نەتوانرا کاردانەوەکە بنێردرێت" + "reaction_failed": "نەتوانرا کاردانەوەکە بنێردرێت", + "native_offer_title": "لێدەری نوێ تاقی دەکەیتەوە؟", + "native_offer_body": "دوگمە و لیستەکانی Liquid Glassی Apple. هەر کاتێک دەتوانیت لە ڕێکخستنەکان ← لێدەر بیگۆڕیت.", + "native_offer_try": "تاقیکردنەوە", + "native_offer_not_now": "ئێستا نا", + "trying_again": "دووبارە هەوڵدانەوە (هەوڵی {})", + "try_mpv": "تاقیکردنەوە بە بزوێنەری mpv", + "ok_to_skip": "OK بۆ تێپەڕاندن", + "quality_lowered": "کوالیتی دابەزێنرا", + "undo": "پاشگەزبوونەوە", + "crash_log": "تۆماری تێکچوون", + "crash_log_empty": "هیچ تێکچوونێک تۆمار نەکراوە" }, "profile": { "title": "پرۆفایل", @@ -679,6 +698,15 @@ "player_engine_section": "بزوێنەری لێدان", "ask_engine_on_play": "هەموو جارێک بپرسە", "ask_engine_on_play_desc": "هەر جارێک ڤیدیۆیەک دەستپێدەکات بزوێنەر هەڵبژێرە", + "controls_style": "شێوازی دوگمەکانی لێدەر", + "controls_style_desc": "کاتێک جاری داهاتوو لێدەر دەکەیتەوە جێبەجێ دەبێت.", + "controls_style_native": "ڕەسەن (iOS 26)", + "controls_style_native_desc": "دوگمە و لیست و پەڕەکانی Liquid Glassی خودی مۆبایلەکە", + "controls_style_system": "شووشەی سیستەم", + "controls_style_system_desc": "دوگمەکانی ئەپ لەسەر Liquid Glassی مۆبایلەکە", + "controls_style_classic": "شووشەی کلاسیک", + "controls_style_classic_desc": "شووشەی تایبەتی ئەپ، وەک وەشانەکانی پێشوو", + "controls_style_needs_ios26": "پێویستی بە iOS 26 یان نوێترە", "playback_section": "لێدان", "default_speed": "خێرایی بنەڕەت", "default_speed_desc": "ئەو خێراییەی هەموو ڤیدیۆیەک پێی دەستپێدەکات", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "لە لای چەپ بۆ سەرەوە و خوارەوە ڕایبکێشە", "volume_gesture": "ڕاکێشانی دەنگ", "volume_gesture_desc": "لە لای ڕاست بۆ سەرەوە و خوارەوە ڕایبکێشە", + "auto_pip": "وێنە لەناو وێنەی خۆکار", + "auto_pip_desc": "ئەگەر لە کاتی لێدانی ڤیدیۆ دوگمەی سەرەکی دابگریت، لە پەنجەرەیەکی بچووکدا بەردەوام دەبێت", "seconds_short": "{} چرکە", "subtitle_style_note": "ئەمانە بۆ هەموو ڤیدیۆیەک جێبەجێ دەبن. کاتبەندیی ژێرنووس بۆ هەر ئەڵقەیەک جیایە و لەناو لێدەر دادەنرێت.", "searching_all_sources": "گەڕان لە هەموو سەرچاوەکان", @@ -2056,7 +2086,8 @@ "title": "لیستەکانم", "remove": "لابردن", "empty_title": "هێشتا هیچ شتێک پاشەکەوت نەکراوە", - "empty_subtitle": "ئەو ناونیشانانەی پاشەکەوتیان دەکەیت لێرە دەردەکەون." + "empty_subtitle": "ئەو ناونیشانانەی پاشەکەوتیان دەکەیت لێرە دەردەکەون.", + "load_failed": "ئەم لیستە بارنەکرا." }, "torrent": { "title": "تۆرێنتەکان", @@ -2910,6 +2941,8 @@ "priming_title": "هیچ ئەڵقەیەکی نوێ لەدەست مەدە", "priming_body": "Sozo هەر کە شتێک کە بەدوایدا دەچیت ئەڵقە یان بەشێکی نوێی هەبوو پێت دەڵێت — و هیچ شتێک کە داوات نەکردبێت.", "priming_body_title": "هەر کە {} ئەڵقەیەکی نوێی هەبوو ئاگادار بکرێیتەوە — و هیچ شتێک کە داوات نەکردبێت.", + "priming_download_title": "بزانە کەی داگرتنەکەت تەواو دەبێت", + "priming_download_body": "Sozo پێشکەوتنی داگرتن لە ئاگادارکردنەوەکاندا پیشان دەدات و پێت دەڵێت کەی ئامادەیە بۆ سەیرکردنی بێ ئینتەرنێت. هەروەها دەتوانێت ئاگادارت بکاتەوە لە ئەڵقە نوێیەکانی ئەوەی بەدوایدا دەچیت.", "priming_point_releases": "ئەڵقە نوێکان، لەگەڵ پۆستەرەکە", "priming_point_quiet": "کاتژمێرە بێدەنگەکان", "priming_point_control": "هەر ناونیشانێک بێدەنگ بکە", @@ -3138,7 +3171,8 @@ "error_attachment_count": "لە هەر نامەیەکدا زۆرترین 5 وێنەی شاشە.", "suggest": "دووبارە دەبێتەوە؟ بۆ پشتگیری بنووسە", "error_offline": "تۆ دەرهێڵیت یان پەیوەندی پچڕا. ئینتەرنێتەکەت بپشکنە و دووبارە هەوڵ بدەرەوە.", - "not_sent": "نەنێردرا: {}" + "not_sent": "نەنێردرا: {}", + "diag_crash_log": "بینینی تۆماری تێکچوون" }, "zangetsu": { "title": "سەرچاوەکانی Zangetsu", diff --git a/assets/translations/de.json b/assets/translations/de.json index 266f00e4..78d358cc 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -34,7 +34,9 @@ "remove": "Entfernen", "show": "Anzeigen", "hide": "Ausblenden", - "installed": "Installiert" + "installed": "Installiert", + "load_more_failed": "Mehr konnte nicht geladen werden", + "go_home": "Zur Startseite" }, "auth": { "login": "Anmelden", @@ -306,7 +308,11 @@ "no_source": "Keine Quelle für diese Folge.", "empty_url": "Diese Quelle hat keine Videoadresse geliefert.", "engine_unavailable": "Der Player ist nicht gestartet. Bitte starte die App vollständig neu.", - "unknown": "Video konnte nicht geladen werden." + "unknown": "Video konnte nicht geladen werden.", + "not_found": "Dieses Video ist auf diesem Server nicht mehr verfügbar. Versuche einen anderen Server.", + "server_error": "Der Videoserver hat Probleme. Versuche es gleich noch einmal oder wähle einen anderen Server.", + "resolve_failed": "Von dieser Quelle konnte kein Videolink abgerufen werden.", + "details": "Details" }, "play": "Abspielen", "pause": "Pause", @@ -570,6 +576,8 @@ "subtitle_n": "Untertitel {}", "subtitle_in_video": "Im Video", "rewind": "Zurückspulen", + "rewind_by": "{} s zurück", + "forward_by": "{} s vor", "mini_restore": "Zurück zum großen Fenster", "pip_unavailable": "Bild-in-Bild konnte für dieses Video nicht gestartet werden.", "scrub_release_cancel": "Loslassen zum Abbrechen", @@ -585,7 +593,18 @@ "reaction_text_hint": "Kurz etwas sagen…", "reaction_wait": "Warte ein paar Sekunden, bevor du erneut reagierst", "reaction_limit": "Du hast das Reaktionslimit für diese Folge erreicht", - "reaction_failed": "Reaktion konnte nicht gesendet werden" + "reaction_failed": "Reaktion konnte nicht gesendet werden", + "native_offer_title": "Den neuen Player ausprobieren?", + "native_offer_body": "Apples Liquid-Glass-Bedienelemente und -Menüs. Du kannst das jederzeit unter Einstellungen → Player ändern.", + "native_offer_try": "Ausprobieren", + "native_offer_not_now": "Nicht jetzt", + "trying_again": "Neuer Versuch (Versuch {})", + "try_mpv": "Mit der mpv-Engine versuchen", + "ok_to_skip": "OK zum Überspringen", + "quality_lowered": "Qualität reduziert", + "undo": "Rückgängig", + "crash_log": "Absturzprotokoll", + "crash_log_empty": "Keine Abstürze aufgezeichnet" }, "profile": { "title": "Profil", @@ -677,6 +696,15 @@ "player_engine_section": "WIEDERGABE-ENGINE", "ask_engine_on_play": "Jedes Mal fragen", "ask_engine_on_play_desc": "Die Engine bei jedem Videostart wählen", + "controls_style": "STIL DER PLAYER-STEUERUNG", + "controls_style_desc": "Gilt ab dem nächsten Öffnen des Players.", + "controls_style_native": "Nativ (iOS 26)", + "controls_style_native_desc": "Die Liquid-Glass-Tasten, Menüs und Sheets des Geräts", + "controls_style_system": "Systemglas", + "controls_style_system_desc": "Die Tasten der App auf dem Liquid Glass des Geräts", + "controls_style_classic": "Klassisches Glas", + "controls_style_classic_desc": "Das eigene Glas der App, wie in früheren Versionen", + "controls_style_needs_ios26": "Erfordert iOS 26 oder neuer", "playback_section": "WIEDERGABE", "default_speed": "Standardtempo", "default_speed_desc": "Tempo, mit dem jedes Video startet", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Auf der linken Seite hoch und runter wischen", "volume_gesture": "Lautstärke wischen", "volume_gesture_desc": "Auf der rechten Seite hoch und runter wischen", + "auto_pip": "Automatisches Bild-im-Bild", + "auto_pip_desc": "Wenn du während eines Videos zum Startbildschirm wechselst, läuft es in einem kleinen Fenster weiter", "seconds_short": "{} s", "subtitle_style_note": "Das gilt für jedes Video. Das Untertitel-Timing bleibt pro Folge und wird im Player eingestellt.", "searching_all_sources": "alle Quellen werden durchsucht", @@ -2056,7 +2086,8 @@ "title": "Meine Listen", "remove": "Entfernen", "empty_title": "Noch nichts gespeichert", - "empty_subtitle": "Titel, die du speicherst, erscheinen hier." + "empty_subtitle": "Titel, die du speicherst, erscheinen hier.", + "load_failed": "Diese Liste konnte nicht geladen werden." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "Keine neue Folge mehr verpassen", "priming_body": "Sozo sagt dir Bescheid, sobald etwas, dem du folgst, eine neue Folge oder ein neues Kapitel bekommt – und sonst nichts, worum du nicht gebeten hast.", "priming_body_title": "Erfahre sofort, wenn „{}“ eine neue Folge bekommt – und sonst nichts, worum du nicht gebeten hast.", + "priming_download_title": "Erfahre, wann dein Download fertig ist", + "priming_download_body": "Sozo zeigt den Download-Fortschritt in deinen Benachrichtigungen und sagt dir, wann du offline schauen kannst. Auf Wunsch auch, wenn Titel, denen du folgst, neue Folgen bekommen.", "priming_point_releases": "Neue Folgen mit Poster", "priming_point_quiet": "Ruhezeit", "priming_point_control": "Jeden Titel stummschalten", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Höchstens 5 Screenshots pro Nachricht.", "suggest": "Passiert das öfter? Schreib dem Support", "error_offline": "Du bist offline oder die Verbindung ist abgebrochen. Prüfe dein Internet und versuche es erneut.", - "not_sent": "Nicht gesendet: {}" + "not_sent": "Nicht gesendet: {}", + "diag_crash_log": "Absturzprotokoll ansehen" }, "zangetsu": { "title": "Zangetsu-Quellen", diff --git a/assets/translations/en.json b/assets/translations/en.json index 32dda706..c91759f6 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -34,7 +34,9 @@ "remove": "Remove", "show": "Show", "hide": "Hide", - "installed": "Installed" + "installed": "Installed", + "load_more_failed": "Couldn't load more", + "go_home": "Go home" }, "auth": { "login": "Login", @@ -306,7 +308,11 @@ "no_source": "No source for this episode.", "empty_url": "This source returned no video address.", "engine_unavailable": "The player did not start. Please fully restart the app.", - "unknown": "Could not load video." + "unknown": "Could not load video.", + "not_found": "This video is no longer on this server. Try another server.", + "server_error": "The video server is having problems. Try again in a moment, or pick another server.", + "resolve_failed": "Couldn't get a video link from this source.", + "details": "Details" }, "play": "Play", "pause": "Pause", @@ -570,6 +576,8 @@ "subtitle_n": "Subtitle {}", "subtitle_in_video": "In the video", "rewind": "Rewind", + "rewind_by": "Rewind {}s", + "forward_by": "Forward {}s", "mini_restore": "Back to the full window", "pip_unavailable": "Picture-in-picture could not start for this video.", "scrub_release_cancel": "Release to cancel", @@ -585,7 +593,18 @@ "reaction_text_hint": "Say something short…", "reaction_wait": "Wait a few seconds before reacting again", "reaction_limit": "You've reached the reaction limit for this episode", - "reaction_failed": "Couldn't send the reaction" + "reaction_failed": "Couldn't send the reaction", + "native_offer_title": "Try the new player?", + "native_offer_body": "Apple's Liquid Glass controls and menus. You can change this anytime in Settings → Player.", + "native_offer_try": "Try it", + "native_offer_not_now": "Not now", + "trying_again": "Trying again (attempt {})", + "try_mpv": "Try with the mpv engine", + "ok_to_skip": "OK to skip", + "quality_lowered": "Lowered quality", + "undo": "Undo", + "crash_log": "Crash log", + "crash_log_empty": "No crashes recorded" }, "profile": { "title": "Profile", @@ -679,6 +698,15 @@ "player_engine_section": "PLAYBACK ENGINE", "ask_engine_on_play": "Ask every time", "ask_engine_on_play_desc": "Choose the engine each time a video starts", + "controls_style": "PLAYER CONTROLS STYLE", + "controls_style_desc": "Takes effect the next time you open the player.", + "controls_style_native": "Native (iOS 26)", + "controls_style_native_desc": "The phone's own Liquid Glass buttons, menus and sheets", + "controls_style_system": "System glass", + "controls_style_system_desc": "The app's buttons on the phone's own Liquid Glass", + "controls_style_classic": "Classic glass", + "controls_style_classic_desc": "The app's own glass, as in earlier versions", + "controls_style_needs_ios26": "Needs iOS 26 or later", "playback_section": "PLAYBACK", "default_speed": "Default speed", "default_speed_desc": "Speed every video starts at", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "Swipe up and down on the left side", "volume_gesture": "Volume swipe", "volume_gesture_desc": "Swipe up and down on the right side", + "auto_pip": "Auto picture-in-picture", + "auto_pip_desc": "Pressing Home while a video plays keeps it playing in a small window", "seconds_short": "{}s", "subtitle_style_note": "These apply to every video. Subtitle timing stays per episode and is set inside the player.", "searching_all_sources": "searching all sources", @@ -2056,7 +2086,8 @@ "title": "My Lists", "remove": "Remove", "empty_title": "Nothing saved yet", - "empty_subtitle": "Titles you save show up here." + "empty_subtitle": "Titles you save show up here.", + "load_failed": "Couldn't load this list." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "Never miss a new episode", "priming_body": "Sozo tells you the moment something you follow gets a new episode or chapter — and nothing you haven't asked for.", "priming_body_title": "Get a heads-up the moment {} gets a new episode — and nothing you haven't asked for.", + "priming_download_title": "Know when your download is done", + "priming_download_body": "Sozo shows download progress in your notifications and tells you when it's ready to watch offline. It can also tell you about new episodes of what you follow.", "priming_point_releases": "New episodes, with the poster", "priming_point_quiet": "Quiet hours", "priming_point_control": "Mute any title", @@ -3138,7 +3171,8 @@ "error_attachment_count": "At most 5 screenshots per message.", "suggest": "Keeps happening? Write to support", "error_offline": "You're offline or the connection dropped. Check your internet and try again.", - "not_sent": "Not sent: {}" + "not_sent": "Not sent: {}", + "diag_crash_log": "View crash log" }, "zangetsu": { "title": "Zangetsu Sources", diff --git a/assets/translations/es.json b/assets/translations/es.json index c464b7de..16ffea50 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -34,7 +34,9 @@ "remove": "Quitar", "show": "Mostrar", "hide": "Ocultar", - "installed": "Instalado" + "installed": "Instalado", + "load_more_failed": "No se pudo cargar más", + "go_home": "Ir al inicio" }, "auth": { "login": "Iniciar sesión", @@ -306,7 +308,11 @@ "no_source": "No hay fuente para este episodio.", "empty_url": "Esta fuente no devolvió ninguna dirección de vídeo.", "engine_unavailable": "El reproductor no arrancó. Reinicia la app por completo.", - "unknown": "No se pudo cargar el vídeo." + "unknown": "No se pudo cargar el vídeo.", + "not_found": "Este vídeo ya no está en este servidor. Prueba con otro servidor.", + "server_error": "El servidor de vídeo tiene problemas. Vuelve a intentarlo en un momento o elige otro servidor.", + "resolve_failed": "No se pudo obtener un enlace de vídeo de esta fuente.", + "details": "Detalles" }, "play": "Reproducir", "pause": "Pausar", @@ -570,6 +576,8 @@ "subtitle_n": "Subtítulo {}", "subtitle_in_video": "En el vídeo", "rewind": "Retroceder", + "rewind_by": "Retroceder {} s", + "forward_by": "Adelantar {} s", "mini_restore": "Volver a la ventana completa", "pip_unavailable": "No se pudo iniciar imagen en imagen para este vídeo.", "scrub_release_cancel": "Suelta para cancelar", @@ -585,7 +593,18 @@ "reaction_text_hint": "Di algo breve…", "reaction_wait": "Espera unos segundos antes de volver a reaccionar", "reaction_limit": "Has alcanzado el límite de reacciones de este episodio", - "reaction_failed": "No se pudo enviar la reacción" + "reaction_failed": "No se pudo enviar la reacción", + "native_offer_title": "¿Probar el nuevo reproductor?", + "native_offer_body": "Los controles y menús Liquid Glass de Apple. Puedes cambiarlo cuando quieras en Ajustes → Reproductor.", + "native_offer_try": "Probar", + "native_offer_not_now": "Ahora no", + "trying_again": "Reintentando (intento {})", + "try_mpv": "Probar con el motor mpv", + "ok_to_skip": "OK para saltar", + "quality_lowered": "Calidad reducida", + "undo": "Deshacer", + "crash_log": "Registro de fallos", + "crash_log_empty": "No hay fallos registrados" }, "profile": { "title": "Perfil", @@ -677,6 +696,15 @@ "player_engine_section": "MOTOR DE REPRODUCCIÓN", "ask_engine_on_play": "Preguntar siempre", "ask_engine_on_play_desc": "Elige el motor cada vez que empieza un vídeo", + "controls_style": "ESTILO DE LOS CONTROLES DEL REPRODUCTOR", + "controls_style_desc": "Se aplica la próxima vez que abras el reproductor.", + "controls_style_native": "Nativo (iOS 26)", + "controls_style_native_desc": "Los botones, menús y hojas Liquid Glass del propio teléfono", + "controls_style_system": "Cristal del sistema", + "controls_style_system_desc": "Los botones de la app sobre el Liquid Glass del teléfono", + "controls_style_classic": "Cristal clásico", + "controls_style_classic_desc": "El cristal propio de la app, como en versiones anteriores", + "controls_style_needs_ios26": "Requiere iOS 26 o posterior", "playback_section": "REPRODUCCIÓN", "default_speed": "Velocidad por defecto", "default_speed_desc": "Velocidad con la que empieza cada vídeo", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Desliza arriba y abajo en el lado izquierdo", "volume_gesture": "Deslizar para el volumen", "volume_gesture_desc": "Desliza arriba y abajo en el lado derecho", + "auto_pip": "Imagen en imagen automática", + "auto_pip_desc": "Al pulsar Inicio mientras se reproduce un vídeo, sigue en una ventana pequeña", "seconds_short": "{} s", "subtitle_style_note": "Esto vale para todos los vídeos. La sincronía de subtítulos se guarda por episodio y se ajusta dentro del reproductor.", "searching_all_sources": "buscando en todas las fuentes", @@ -2056,7 +2086,8 @@ "title": "Mis listas", "remove": "Quitar", "empty_title": "Nada guardado todavía", - "empty_subtitle": "Los títulos que guardes aparecerán aquí." + "empty_subtitle": "Los títulos que guardes aparecerán aquí.", + "load_failed": "No se pudo cargar esta lista." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "No te pierdas ni un episodio", "priming_body": "Sozo te avisa en cuanto algo que sigues tenga un episodio o capítulo nuevo, y nada que no hayas pedido.", "priming_body_title": "Entérate en cuanto «{}» tenga un episodio nuevo, y nada que no hayas pedido.", + "priming_download_title": "Entérate cuando termine tu descarga", + "priming_download_body": "Sozo muestra el progreso de la descarga en tus notificaciones y te avisa cuando esté lista para ver sin conexión. También puede avisarte de nuevos episodios de lo que sigues.", "priming_point_releases": "Episodios nuevos, con póster", "priming_point_quiet": "Horas de silencio", "priming_point_control": "Silencia cualquier título", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Como máximo 5 capturas por mensaje.", "suggest": "¿Sigue pasando? Escribe a soporte", "error_offline": "No tienes conexión o se cortó. Revisa tu internet e inténtalo de nuevo.", - "not_sent": "No se envió: {}" + "not_sent": "No se envió: {}", + "diag_crash_log": "Ver registro de fallos" }, "zangetsu": { "title": "Fuentes de Zangetsu", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index e1a47c74..5aa0e570 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -34,7 +34,9 @@ "remove": "Retirer", "show": "Afficher", "hide": "Masquer", - "installed": "Installé" + "installed": "Installé", + "load_more_failed": "Impossible d’en charger plus", + "go_home": "Retour à l’accueil" }, "auth": { "login": "Connexion", @@ -306,7 +308,11 @@ "no_source": "Aucune source pour cet épisode.", "empty_url": "Cette source n’a renvoyé aucune adresse vidéo.", "engine_unavailable": "Le lecteur n’a pas démarré. Redémarre complètement l’appli.", - "unknown": "Impossible de charger la vidéo." + "unknown": "Impossible de charger la vidéo.", + "not_found": "Cette vidéo n'est plus sur ce serveur. Essayez un autre serveur.", + "server_error": "Le serveur vidéo rencontre des problèmes. Réessayez dans un instant ou choisissez un autre serveur.", + "resolve_failed": "Impossible d'obtenir un lien vidéo depuis cette source.", + "details": "Détails" }, "play": "Lire", "pause": "Pause", @@ -570,6 +576,8 @@ "subtitle_n": "Sous-titres {}", "subtitle_in_video": "Dans la vidéo", "rewind": "Reculer", + "rewind_by": "Reculer de {} s", + "forward_by": "Avancer de {} s", "mini_restore": "Revenir à la fenêtre complète", "pip_unavailable": "Impossible de lancer l’image dans l’image pour cette vidéo.", "scrub_release_cancel": "Relâchez pour annuler", @@ -585,7 +593,18 @@ "reaction_text_hint": "Un mot rapide…", "reaction_wait": "Attendez quelques secondes avant de réagir à nouveau", "reaction_limit": "Vous avez atteint la limite de réactions pour cet épisode", - "reaction_failed": "Impossible d'envoyer la réaction" + "reaction_failed": "Impossible d'envoyer la réaction", + "native_offer_title": "Essayer le nouveau lecteur ?", + "native_offer_body": "Les commandes et menus Liquid Glass d’Apple. Vous pouvez changer cela à tout moment dans Réglages → Lecteur.", + "native_offer_try": "Essayer", + "native_offer_not_now": "Plus tard", + "trying_again": "Nouvel essai (tentative {})", + "try_mpv": "Essayer avec le moteur mpv", + "ok_to_skip": "OK pour passer", + "quality_lowered": "Qualité réduite", + "undo": "Annuler", + "crash_log": "Journal des plantages", + "crash_log_empty": "Aucun plantage enregistré" }, "profile": { "title": "Profil", @@ -677,6 +696,15 @@ "player_engine_section": "MOTEUR DE LECTURE", "ask_engine_on_play": "Demander à chaque fois", "ask_engine_on_play_desc": "Choisir le moteur à chaque démarrage de vidéo", + "controls_style": "STYLE DES COMMANDES DU LECTEUR", + "controls_style_desc": "S'applique à la prochaine ouverture du lecteur.", + "controls_style_native": "Natif (iOS 26)", + "controls_style_native_desc": "Les boutons, menus et feuilles Liquid Glass du téléphone", + "controls_style_system": "Verre système", + "controls_style_system_desc": "Les boutons de l'app sur le Liquid Glass du téléphone", + "controls_style_classic": "Verre classique", + "controls_style_classic_desc": "Le verre propre à l'app, comme dans les versions précédentes", + "controls_style_needs_ios26": "Nécessite iOS 26 ou version ultérieure", "playback_section": "LECTURE", "default_speed": "Vitesse par défaut", "default_speed_desc": "Vitesse à laquelle chaque vidéo démarre", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Glisse de haut en bas sur le côté gauche", "volume_gesture": "Glisser pour le volume", "volume_gesture_desc": "Glisse de haut en bas sur le côté droit", + "auto_pip": "Image dans l’image automatique", + "auto_pip_desc": "Appuyer sur Accueil pendant une vidéo la poursuit dans une petite fenêtre", "seconds_short": "{} s", "subtitle_style_note": "Cela vaut pour toutes les vidéos. Le calage des sous-titres reste propre à chaque épisode et se règle dans le lecteur.", "searching_all_sources": "recherche dans toutes les sources", @@ -2056,7 +2086,8 @@ "title": "Mes listes", "remove": "Retirer", "empty_title": "Rien d’enregistré pour l’instant", - "empty_subtitle": "Les titres que tu enregistres apparaissent ici." + "empty_subtitle": "Les titres que tu enregistres apparaissent ici.", + "load_failed": "Impossible de charger cette liste." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "Ne ratez plus aucun épisode", "priming_body": "Sozo vous prévient dès qu’un titre que vous suivez a un nouvel épisode ou chapitre — et rien que vous n’ayez pas demandé.", "priming_body_title": "Soyez prévenu dès que « {} » a un nouvel épisode — et rien que vous n’ayez pas demandé.", + "priming_download_title": "Sachez quand votre téléchargement est terminé", + "priming_download_body": "Sozo affiche la progression du téléchargement dans vos notifications et vous prévient quand il est prêt à regarder hors ligne. Il peut aussi vous signaler les nouveaux épisodes de ce que vous suivez.", "priming_point_releases": "Nouveaux épisodes, avec l’affiche", "priming_point_quiet": "Heures calmes", "priming_point_control": "Coupez n’importe quel titre", @@ -3138,7 +3171,8 @@ "error_attachment_count": "5 captures maximum par message.", "suggest": "Ça se reproduit ? Écris au support", "error_offline": "Vous êtes hors ligne ou la connexion a été coupée. Vérifiez votre connexion et réessayez.", - "not_sent": "Non envoyé : {}" + "not_sent": "Non envoyé : {}", + "diag_crash_log": "Voir le journal des plantages" }, "zangetsu": { "title": "Sources Zangetsu", diff --git a/assets/translations/hi.json b/assets/translations/hi.json index 83f534df..ccf57325 100644 --- a/assets/translations/hi.json +++ b/assets/translations/hi.json @@ -34,7 +34,9 @@ "remove": "हटाएँ", "show": "दिखाएँ", "hide": "छिपाएँ", - "installed": "इंस्टॉल है" + "installed": "इंस्टॉल है", + "load_more_failed": "और लोड नहीं हो सका", + "go_home": "होम पर जाएँ" }, "auth": { "login": "साइन इन", @@ -306,7 +308,11 @@ "no_source": "इस एपिसोड के लिए कोई सोर्स नहीं है।", "empty_url": "इस सोर्स से वीडियो का कोई पता नहीं मिला।", "engine_unavailable": "प्लेयर शुरू नहीं हुआ। कृपया ऐप को पूरी तरह बंद करके फिर से खोलें।", - "unknown": "वीडियो लोड नहीं हो पाया।" + "unknown": "वीडियो लोड नहीं हो पाया।", + "not_found": "यह वीडियो अब इस सर्वर पर नहीं है। कोई दूसरा सर्वर आज़माएँ।", + "server_error": "वीडियो सर्वर में समस्या है। थोड़ी देर में फिर कोशिश करें या कोई दूसरा सर्वर चुनें।", + "resolve_failed": "इस स्रोत से वीडियो लिंक नहीं मिल पाया।", + "details": "विवरण" }, "play": "चलाएँ", "pause": "रोकें", @@ -570,6 +576,8 @@ "subtitle_n": "सबटाइटल {}", "subtitle_in_video": "वीडियो में", "rewind": "रिवाइंड", + "rewind_by": "{} सेकंड पीछे", + "forward_by": "{} सेकंड आगे", "mini_restore": "वापस पूरी विंडो में", "pip_unavailable": "इस वीडियो के लिए पिक्चर-इन-पिक्चर शुरू नहीं हो पाया।", "scrub_release_cancel": "रद्द करने के लिए छोड़ें", @@ -585,7 +593,18 @@ "reaction_text_hint": "कुछ छोटा लिखें…", "reaction_wait": "फिर से प्रतिक्रिया देने से पहले कुछ सेकंड रुकें", "reaction_limit": "इस एपिसोड के लिए प्रतिक्रियाओं की सीमा पूरी हो गई", - "reaction_failed": "प्रतिक्रिया नहीं भेजी जा सकी" + "reaction_failed": "प्रतिक्रिया नहीं भेजी जा सकी", + "native_offer_title": "नया प्लेयर आज़माएँ?", + "native_offer_body": "Apple के Liquid Glass कंट्रोल और मेन्यू। आप इसे कभी भी सेटिंग्स → प्लेयर में बदल सकते हैं।", + "native_offer_try": "आज़माएँ", + "native_offer_not_now": "अभी नहीं", + "trying_again": "फिर से कोशिश हो रही है (प्रयास {})", + "try_mpv": "mpv इंजन से आज़माएँ", + "ok_to_skip": "छोड़ने के लिए OK", + "quality_lowered": "क्वालिटी कम की गई", + "undo": "पूर्ववत करें", + "crash_log": "क्रैश लॉग", + "crash_log_empty": "कोई क्रैश दर्ज नहीं" }, "profile": { "title": "प्रोफ़ाइल", @@ -679,6 +698,15 @@ "player_engine_section": "प्लेबैक इंजन", "ask_engine_on_play": "हर बार पूछें", "ask_engine_on_play_desc": "हर बार वीडियो शुरू होने पर इंजन चुनें", + "controls_style": "प्लेयर कंट्रोल की शैली", + "controls_style_desc": "अगली बार प्लेयर खोलने पर लागू होगा।", + "controls_style_native": "नेटिव (iOS 26)", + "controls_style_native_desc": "फ़ोन के अपने Liquid Glass बटन, मेन्यू और शीट", + "controls_style_system": "सिस्टम ग्लास", + "controls_style_system_desc": "फ़ोन के अपने Liquid Glass पर ऐप के बटन", + "controls_style_classic": "क्लासिक ग्लास", + "controls_style_classic_desc": "ऐप का अपना ग्लास, पिछले वर्ज़न की तरह", + "controls_style_needs_ios26": "iOS 26 या नया चाहिए", "playback_section": "प्लेबैक", "default_speed": "डिफ़ॉल्ट स्पीड", "default_speed_desc": "हर वीडियो इसी स्पीड से शुरू होगा", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "बाईं तरफ़ ऊपर-नीचे स्वाइप करें", "volume_gesture": "वॉल्यूम स्वाइप", "volume_gesture_desc": "दाईं तरफ़ ऊपर-नीचे स्वाइप करें", + "auto_pip": "स्वचालित पिक्चर-इन-पिक्चर", + "auto_pip_desc": "वीडियो चलते समय होम दबाने पर वह एक छोटी विंडो में चलता रहता है", "seconds_short": "{} से", "subtitle_style_note": "ये हर वीडियो पर लागू होते हैं। सबटाइटल की टाइमिंग हर एपिसोड के लिए अलग रहती है और प्लेयर के अंदर सेट होती है।", "searching_all_sources": "सभी सोर्स में खोज रहे हैं", @@ -2056,7 +2086,8 @@ "title": "मेरी लिस्ट", "remove": "हटाएँ", "empty_title": "अभी तक कुछ सेव नहीं किया", - "empty_subtitle": "आपके सेव किए गए टाइटल यहाँ दिखेंगे।" + "empty_subtitle": "आपके सेव किए गए टाइटल यहाँ दिखेंगे।", + "load_failed": "यह सूची लोड नहीं हो सकी।" }, "torrent": { "title": "टोरेंट", @@ -2910,6 +2941,8 @@ "priming_title": "कोई नया एपिसोड न छूटे", "priming_body": "आपकी फ़ॉलो की हुई किसी चीज़ का नया एपिसोड या चैप्टर आते ही Sozo आपको बताता है — और ऐसा कुछ नहीं जो आपने माँगा न हो।", "priming_body_title": "{} का नया एपिसोड आते ही आपको ख़बर मिलेगी — और ऐसा कुछ नहीं जो आपने माँगा न हो।", + "priming_download_title": "जानें कि आपका डाउनलोड कब पूरा हुआ", + "priming_download_body": "Sozo सूचनाओं में डाउनलोड की प्रगति दिखाता है और बताता है कि ऑफ़लाइन देखने के लिए कब तैयार है। यह आपके फ़ॉलो किए गए शो के नए एपिसोड के बारे में भी बता सकता है।", "priming_point_releases": "नए एपिसोड, पोस्टर के साथ", "priming_point_quiet": "शांत समय", "priming_point_control": "कोई भी टाइटल म्यूट करें", @@ -3138,7 +3171,8 @@ "error_attachment_count": "एक मैसेज में ज़्यादा से ज़्यादा 5 स्क्रीनशॉट।", "suggest": "बार-बार हो रहा है? सपोर्ट को लिखें", "error_offline": "आप ऑफ़लाइन हैं या कनेक्शन टूट गया। इंटरनेट जाँचें और फिर से कोशिश करें।", - "not_sent": "नहीं भेजा गया: {}" + "not_sent": "नहीं भेजा गया: {}", + "diag_crash_log": "क्रैश लॉग देखें" }, "zangetsu": { "title": "Zangetsu स्रोत", diff --git a/assets/translations/id.json b/assets/translations/id.json index 89ecd4ce..f1993c42 100644 --- a/assets/translations/id.json +++ b/assets/translations/id.json @@ -34,7 +34,9 @@ "remove": "Hapus", "show": "Tampilkan", "hide": "Sembunyikan", - "installed": "Terpasang" + "installed": "Terpasang", + "load_more_failed": "Tidak dapat memuat lebih banyak", + "go_home": "Ke beranda" }, "auth": { "login": "Masuk", @@ -306,7 +308,11 @@ "no_source": "Tidak ada sumber untuk episode ini.", "empty_url": "Sumber ini tidak mengembalikan alamat video.", "engine_unavailable": "Pemutar tidak jalan. Mulai ulang aplikasi sepenuhnya.", - "unknown": "Video tidak bisa dimuat." + "unknown": "Video tidak bisa dimuat.", + "not_found": "Video ini sudah tidak ada di server ini. Coba server lain.", + "server_error": "Server video sedang bermasalah. Coba lagi sebentar lagi atau pilih server lain.", + "resolve_failed": "Tidak bisa mendapatkan tautan video dari sumber ini.", + "details": "Detail" }, "play": "Putar", "pause": "Jeda", @@ -570,6 +576,8 @@ "subtitle_n": "Subtitel {}", "subtitle_in_video": "Di dalam video", "rewind": "Mundur", + "rewind_by": "Mundur {} dtk", + "forward_by": "Maju {} dtk", "mini_restore": "Kembali ke jendela penuh", "pip_unavailable": "Gambar-dalam-gambar tidak bisa dimulai untuk video ini.", "scrub_release_cancel": "Lepas untuk membatalkan", @@ -585,7 +593,18 @@ "reaction_text_hint": "Tulis sesuatu yang singkat…", "reaction_wait": "Tunggu beberapa detik sebelum bereaksi lagi", "reaction_limit": "Kamu sudah mencapai batas reaksi untuk episode ini", - "reaction_failed": "Gagal mengirim reaksi" + "reaction_failed": "Gagal mengirim reaksi", + "native_offer_title": "Coba pemutar baru?", + "native_offer_body": "Kontrol dan menu Liquid Glass dari Apple. Anda bisa mengubahnya kapan saja di Pengaturan → Pemutar.", + "native_offer_try": "Coba", + "native_offer_not_now": "Nanti saja", + "trying_again": "Mencoba lagi (percobaan {})", + "try_mpv": "Coba dengan mesin mpv", + "ok_to_skip": "OK untuk melewati", + "quality_lowered": "Kualitas diturunkan", + "undo": "Urungkan", + "crash_log": "Log kerusakan", + "crash_log_empty": "Tidak ada kerusakan tercatat" }, "profile": { "title": "Profil", @@ -677,6 +696,15 @@ "player_engine_section": "MESIN PEMUTARAN", "ask_engine_on_play": "Tanya setiap kali", "ask_engine_on_play_desc": "Pilih mesinnya setiap kali video dimulai", + "controls_style": "GAYA KONTROL PEMUTAR", + "controls_style_desc": "Berlaku saat pemutar dibuka berikutnya.", + "controls_style_native": "Bawaan (iOS 26)", + "controls_style_native_desc": "Tombol, menu, dan lembar Liquid Glass milik ponsel sendiri", + "controls_style_system": "Kaca sistem", + "controls_style_system_desc": "Tombol aplikasi di atas Liquid Glass milik ponsel", + "controls_style_classic": "Kaca klasik", + "controls_style_classic_desc": "Kaca milik aplikasi, seperti di versi sebelumnya", + "controls_style_needs_ios26": "Perlu iOS 26 atau lebih baru", "playback_section": "PEMUTARAN", "default_speed": "Kecepatan bawaan", "default_speed_desc": "Kecepatan awal setiap video", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Geser naik-turun di sisi kiri", "volume_gesture": "Geser untuk volume", "volume_gesture_desc": "Geser naik-turun di sisi kanan", + "auto_pip": "Gambar-dalam-gambar otomatis", + "auto_pip_desc": "Menekan Beranda saat video diputar membuatnya tetap berjalan di jendela kecil", "seconds_short": "{} dtk", "subtitle_style_note": "Ini berlaku untuk semua video. Sinkron subtitle tetap per episode dan diatur di dalam pemutar.", "searching_all_sources": "mencari di semua sumber", @@ -2056,7 +2086,8 @@ "title": "Daftar saya", "remove": "Singkirkan", "empty_title": "Belum ada yang disimpan", - "empty_subtitle": "Judul yang kamu simpan muncul di sini." + "empty_subtitle": "Judul yang kamu simpan muncul di sini.", + "load_failed": "Tidak dapat memuat daftar ini." }, "torrent": { "title": "Torrent", @@ -2910,6 +2941,8 @@ "priming_title": "Jangan lewatkan episode baru", "priming_body": "Sozo memberi tahu begitu judul yang kamu ikuti punya episode atau bab baru — tanpa hal lain yang tidak kamu minta.", "priming_body_title": "Langsung tahu begitu “{}” punya episode baru — tanpa hal lain yang tidak kamu minta.", + "priming_download_title": "Ketahui saat unduhanmu selesai", + "priming_download_body": "Sozo menampilkan progres unduhan di notifikasi dan memberi tahu saat siap ditonton offline. Sozo juga bisa memberi tahu episode baru dari yang kamu ikuti.", "priming_point_releases": "Episode baru, lengkap dengan poster", "priming_point_quiet": "Jam tenang", "priming_point_control": "Bisukan judul mana pun", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Maksimal 5 tangkapan layar per pesan.", "suggest": "Terus terjadi? Hubungi dukungan", "error_offline": "Kamu sedang offline atau koneksi terputus. Periksa internet lalu coba lagi.", - "not_sent": "Tidak terkirim: {}" + "not_sent": "Tidak terkirim: {}", + "diag_crash_log": "Lihat log kerusakan" }, "zangetsu": { "title": "Sumber Zangetsu", diff --git a/assets/translations/ku.json b/assets/translations/ku.json index 56a7ff83..5e4e47f1 100644 --- a/assets/translations/ku.json +++ b/assets/translations/ku.json @@ -34,7 +34,9 @@ "remove": "Rake", "show": "Nîşan bide", "hide": "Veşêre", - "installed": "Sazkirî" + "installed": "Sazkirî", + "load_more_failed": "Bêtir nehat barkirin", + "go_home": "Biçe destpêkê" }, "auth": { "login": "Têkeve", @@ -306,7 +308,11 @@ "no_source": "Ji bo vê beşê çavkanî tune.", "empty_url": "Vê çavkaniyê tu navnîşana vîdyoyê neda.", "engine_unavailable": "Lêdar dest pê nekir. Ji kerema xwe sepanê bi tevahî ji nû ve bide destpêkirin.", - "unknown": "Vîdyo nehat barkirin." + "unknown": "Vîdyo nehat barkirin.", + "not_found": "Ev vîdyo êdî li ser vê serverê nîne. Serverek din biceribîne.", + "server_error": "Servera vîdyoyê pirsgirêk heye. Piştî demekê dîsa biceribîne an serverek din hilbijêre.", + "resolve_failed": "Ji vê çavkaniyê girêdana vîdyoyê nehat wergirtin.", + "details": "Hûrgilî" }, "play": "Lê bide", "pause": "Rawestîne", @@ -570,6 +576,8 @@ "subtitle_n": "Binnivîs {}", "subtitle_in_video": "Di vîdyoyê de", "rewind": "Paşve bibe", + "rewind_by": "{}s paş", + "forward_by": "{}s pêş", "mini_restore": "Vegere pencereya tevahî", "pip_unavailable": "Wêne di nav wêneyê de ji bo vê vîdyoyê dest pê nekir.", "scrub_release_cancel": "Ji bo betalkirinê berde", @@ -585,7 +593,18 @@ "reaction_text_hint": "Tiştekî kurt binivîse…", "reaction_wait": "Berî ku dîsa bertek bidî çend saniyan bisekine", "reaction_limit": "Te sînorê berteka vê beşê tije kir", - "reaction_failed": "Bertek nehat şandin" + "reaction_failed": "Bertek nehat şandin", + "native_offer_title": "Lêdara nû biceribînî?", + "native_offer_body": "Bişkok û menuyên Liquid Glass ên Apple. Tu dikarî vê her dem di Mîheng → Lêdar de biguherînî.", + "native_offer_try": "Biceribîne", + "native_offer_not_now": "Ne niha", + "trying_again": "Dîsa tê ceribandin (ceribandina {})", + "try_mpv": "Bi motora mpv biceribîne", + "ok_to_skip": "Ji bo derbaskirinê OK", + "quality_lowered": "Kalîte hat kêmkirin", + "undo": "Paşve vegerîne", + "crash_log": "Tomara têkçûnan", + "crash_log_empty": "Tu têkçûn nehatiye tomarkirin" }, "profile": { "title": "Profîl", @@ -679,6 +698,15 @@ "player_engine_section": "MOTORA LÊDANÊ", "ask_engine_on_play": "Her car bipirse", "ask_engine_on_play_desc": "Her car ku vîdyoyek dest pê dike motorê hilbijêre", + "controls_style": "ŞÊWAZA BIŞKOJÊN LÊXISTOKÊ", + "controls_style_desc": "Gava cara din lêxistokê vekî dê derbasdar bibe.", + "controls_style_native": "Xwecihî (iOS 26)", + "controls_style_native_desc": "Bişkok, menû û rûpelên Liquid Glass ên telefonê bi xwe", + "controls_style_system": "Cama pergalê", + "controls_style_system_desc": "Bişkojên sepanê li ser Liquid Glass a telefonê", + "controls_style_classic": "Cama klasîk", + "controls_style_classic_desc": "Cama sepanê bi xwe, wek di guhertoyên berê de", + "controls_style_needs_ios26": "iOS 26 an nûtir pêwîst e", "playback_section": "LÊDAN", "default_speed": "Leza standard", "default_speed_desc": "Leza ku her vîdyo pê dest pê dike", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "Li aliyê çepê ber bi jor û jêr bişemitîne", "volume_gesture": "Şemitandina deng", "volume_gesture_desc": "Li aliyê rastê ber bi jor û jêr bişemitîne", + "auto_pip": "Wêne di nav wêneyê de bixweber", + "auto_pip_desc": "Dema vîdyo dilîze û tu Destpêkê bitikînî, ew di pencereyeke biçûk de didome", "seconds_short": "{}s", "subtitle_style_note": "Ev ji bo her vîdyoyê derbasdar in. Demkirina binnivîsê ji bo her beşê cuda dimîne û di nav lêdarê de tê sazkirin.", "searching_all_sources": "li hemû çavkaniyan tê gerîn", @@ -2056,7 +2086,8 @@ "title": "Lîsteyên min", "remove": "Rake", "empty_title": "Hîn tiştek nehatiye tomarkirin", - "empty_subtitle": "Sernavên ku tu tomar dikî li vir xuya dibin." + "empty_subtitle": "Sernavên ku tu tomar dikî li vir xuya dibin.", + "load_failed": "Ev lîste nehat barkirin." }, "torrent": { "title": "Torrent", @@ -2910,6 +2941,8 @@ "priming_title": "Tu carî beşeke nû ji dest nede", "priming_body": "Sozo di kêliya ku tiştekî ku tu dişopînî beşeke an fesleke nû distîne de ji te re dibêje — û tiştekî ku te nexwestiye na.", "priming_body_title": "Di kêliya ku {} beşeke nû distîne de agahdar bibe — û tiştekî ku te nexwestiye na.", + "priming_download_title": "Bizane kengê daxistina te diqede", + "priming_download_body": "Sozo pêşketina daxistinê di agahdariyan de nîşan dide û dema ku ji bo temaşekirina bê înternet amade be ji te re dibêje. Ew dikare li ser beşên nû yên tiştên ku tu dişopînî jî te agahdar bike.", "priming_point_releases": "Beşên nû, bi posterê", "priming_point_quiet": "Saetên bêdeng", "priming_point_control": "Her sernavê bêdeng bike", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Di peyamekê de herî zêde 5 dîmenên ekranê.", "suggest": "Dubare dibe? Ji piştgiriyê re binivîse", "error_offline": "Tu offline î an girêdan qut bû. Înternetê kontrol bike û dîsa biceribîne.", - "not_sent": "Nehat şandin: {}" + "not_sent": "Nehat şandin: {}", + "diag_crash_log": "Tomara têkçûnan bibîne" }, "zangetsu": { "title": "Çavkaniyên Zangetsu", diff --git a/assets/translations/nl.json b/assets/translations/nl.json index b86ec879..bed20698 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -34,7 +34,9 @@ "remove": "Verwijderen", "show": "Tonen", "hide": "Verbergen", - "installed": "Geïnstalleerd" + "installed": "Geïnstalleerd", + "load_more_failed": "Kon niet meer laden", + "go_home": "Naar start" }, "auth": { "login": "Inloggen", @@ -306,7 +308,11 @@ "no_source": "Geen bron voor deze aflevering.", "empty_url": "Deze bron gaf geen videoadres terug.", "engine_unavailable": "De speler is niet gestart. Start de app volledig opnieuw op.", - "unknown": "Video kon niet worden geladen." + "unknown": "Video kon niet worden geladen.", + "not_found": "Deze video staat niet meer op deze server. Probeer een andere server.", + "server_error": "De videoserver heeft problemen. Probeer het zo opnieuw of kies een andere server.", + "resolve_failed": "Kon geen videolink van deze bron krijgen.", + "details": "Details" }, "play": "Afspelen", "pause": "Pauze", @@ -570,6 +576,8 @@ "subtitle_n": "Ondertiteling {}", "subtitle_in_video": "In de video", "rewind": "Terugspoelen", + "rewind_by": "{} s terug", + "forward_by": "{} s vooruit", "mini_restore": "Terug naar het volledige venster", "pip_unavailable": "Beeld-in-beeld kon voor deze video niet starten.", "scrub_release_cancel": "Loslaten om te annuleren", @@ -585,7 +593,18 @@ "reaction_text_hint": "Iets korts…", "reaction_wait": "Wacht een paar seconden voordat je opnieuw reageert", "reaction_limit": "Je hebt het reactielimiet voor deze aflevering bereikt", - "reaction_failed": "Kon de reactie niet versturen" + "reaction_failed": "Kon de reactie niet versturen", + "native_offer_title": "De nieuwe speler proberen?", + "native_offer_body": "De Liquid Glass-bediening en -menu’s van Apple. Je kunt dit altijd wijzigen via Instellingen → Speler.", + "native_offer_try": "Proberen", + "native_offer_not_now": "Niet nu", + "trying_again": "Opnieuw proberen (poging {})", + "try_mpv": "Proberen met de mpv-engine", + "ok_to_skip": "OK om over te slaan", + "quality_lowered": "Kwaliteit verlaagd", + "undo": "Ongedaan maken", + "crash_log": "Crashlogboek", + "crash_log_empty": "Geen crashes vastgelegd" }, "profile": { "title": "Profiel", @@ -677,6 +696,15 @@ "player_engine_section": "AFSPEELENGINE", "ask_engine_on_play": "Elke keer vragen", "ask_engine_on_play_desc": "Kies de engine telkens als een video start", + "controls_style": "STIJL VAN DE SPELERKNOPPEN", + "controls_style_desc": "Geldt vanaf de volgende keer dat je de speler opent.", + "controls_style_native": "Native (iOS 26)", + "controls_style_native_desc": "De eigen Liquid Glass-knoppen, -menu's en -panelen van de telefoon", + "controls_style_system": "Systeemglas", + "controls_style_system_desc": "De knoppen van de app op het Liquid Glass van de telefoon", + "controls_style_classic": "Klassiek glas", + "controls_style_classic_desc": "Het eigen glas van de app, zoals in eerdere versies", + "controls_style_needs_ios26": "Vereist iOS 26 of nieuwer", "playback_section": "AFSPELEN", "default_speed": "Standaardsnelheid", "default_speed_desc": "Snelheid waarmee elke video begint", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Veeg omhoog en omlaag aan de linkerkant", "volume_gesture": "Volume vegen", "volume_gesture_desc": "Veeg omhoog en omlaag aan de rechterkant", + "auto_pip": "Automatisch beeld-in-beeld", + "auto_pip_desc": "Als je tijdens een video op Home drukt, speelt hij verder in een klein venster", "seconds_short": "{} s", "subtitle_style_note": "Dit geldt voor elke video. De ondertiteltiming blijft per aflevering en wordt in de speler ingesteld.", "searching_all_sources": "alle bronnen doorzoeken", @@ -2056,7 +2086,8 @@ "title": "Mijn lijsten", "remove": "Verwijderen", "empty_title": "Nog niets opgeslagen", - "empty_subtitle": "Titels die je opslaat verschijnen hier." + "empty_subtitle": "Titels die je opslaat verschijnen hier.", + "load_failed": "Kon deze lijst niet laden." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "Mis nooit meer een nieuwe aflevering", "priming_body": "Sozo laat het je weten zodra iets wat je volgt een nieuwe aflevering of een nieuw hoofdstuk krijgt — en niets waar je niet om hebt gevraagd.", "priming_body_title": "Hoor het meteen wanneer ‘{}’ een nieuwe aflevering krijgt — en niets waar je niet om hebt gevraagd.", + "priming_download_title": "Weet wanneer je download klaar is", + "priming_download_body": "Sozo toont de downloadvoortgang in je meldingen en laat weten wanneer je offline kunt kijken. Ook kan Sozo je melden wanneer iets wat je volgt een nieuwe aflevering krijgt.", "priming_point_releases": "Nieuwe afleveringen, met poster", "priming_point_quiet": "Stille uren", "priming_point_control": "Elke titel dempen", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Maximaal 5 screenshots per bericht.", "suggest": "Blijft het gebeuren? Schrijf support", "error_offline": "Je bent offline of de verbinding is verbroken. Controleer je internet en probeer het opnieuw.", - "not_sent": "Niet verzonden: {}" + "not_sent": "Niet verzonden: {}", + "diag_crash_log": "Crashlogboek bekijken" }, "zangetsu": { "title": "Zangetsu-bronnen", diff --git a/assets/translations/pt.json b/assets/translations/pt.json index 01e96d87..593ce761 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -34,7 +34,9 @@ "remove": "Remover", "show": "Mostrar", "hide": "Ocultar", - "installed": "Instalado" + "installed": "Instalado", + "load_more_failed": "Não foi possível carregar mais", + "go_home": "Ir para o início" }, "auth": { "login": "Entrar", @@ -306,7 +308,11 @@ "no_source": "Nenhuma fonte para este episódio.", "empty_url": "Esta fonte não devolveu nenhum endereço de vídeo.", "engine_unavailable": "O player não iniciou. Reinicie o app por completo.", - "unknown": "Não foi possível carregar o vídeo." + "unknown": "Não foi possível carregar o vídeo.", + "not_found": "Este vídeo não está mais neste servidor. Tente outro servidor.", + "server_error": "O servidor de vídeo está com problemas. Tente novamente em instantes ou escolha outro servidor.", + "resolve_failed": "Não foi possível obter um link de vídeo desta fonte.", + "details": "Detalhes" }, "play": "Reproduzir", "pause": "Pausar", @@ -570,6 +576,8 @@ "subtitle_n": "Legenda {}", "subtitle_in_video": "No vídeo", "rewind": "Voltar", + "rewind_by": "Voltar {} s", + "forward_by": "Avançar {} s", "mini_restore": "Voltar à janela inteira", "pip_unavailable": "Não foi possível iniciar o picture-in-picture para este vídeo.", "scrub_release_cancel": "Solte para cancelar", @@ -585,7 +593,18 @@ "reaction_text_hint": "Diga algo curto…", "reaction_wait": "Espere alguns segundos antes de reagir de novo", "reaction_limit": "Você atingiu o limite de reações deste episódio", - "reaction_failed": "Não foi possível enviar a reação" + "reaction_failed": "Não foi possível enviar a reação", + "native_offer_title": "Experimentar o novo player?", + "native_offer_body": "Os controles e menus Liquid Glass da Apple. Você pode mudar isso a qualquer momento em Configurações → Player.", + "native_offer_try": "Experimentar", + "native_offer_not_now": "Agora não", + "trying_again": "Tentando novamente (tentativa {})", + "try_mpv": "Tentar com o motor mpv", + "ok_to_skip": "OK para pular", + "quality_lowered": "Qualidade reduzida", + "undo": "Desfazer", + "crash_log": "Registro de falhas", + "crash_log_empty": "Nenhuma falha registrada" }, "profile": { "title": "Perfil", @@ -677,6 +696,15 @@ "player_engine_section": "MOTOR DE REPRODUÇÃO", "ask_engine_on_play": "Perguntar sempre", "ask_engine_on_play_desc": "Escolha o motor toda vez que um vídeo começar", + "controls_style": "ESTILO DOS CONTROLES DO PLAYER", + "controls_style_desc": "Vale a partir da próxima vez que você abrir o player.", + "controls_style_native": "Nativo (iOS 26)", + "controls_style_native_desc": "Os botões, menus e painéis Liquid Glass do próprio telefone", + "controls_style_system": "Vidro do sistema", + "controls_style_system_desc": "Os botões do app sobre o Liquid Glass do telefone", + "controls_style_classic": "Vidro clássico", + "controls_style_classic_desc": "O vidro próprio do app, como nas versões anteriores", + "controls_style_needs_ios26": "Requer iOS 26 ou posterior", "playback_section": "REPRODUÇÃO", "default_speed": "Velocidade padrão", "default_speed_desc": "Velocidade com que todo vídeo começa", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Deslize para cima e para baixo no lado esquerdo", "volume_gesture": "Deslizar para o volume", "volume_gesture_desc": "Deslize para cima e para baixo no lado direito", + "auto_pip": "Picture-in-picture automático", + "auto_pip_desc": "Ao tocar em Início durante um vídeo, ele continua numa janela pequena", "seconds_short": "{} s", "subtitle_style_note": "Isso vale para todos os vídeos. A sincronia da legenda é por episódio e se ajusta dentro do player.", "searching_all_sources": "buscando em todas as fontes", @@ -2056,7 +2086,8 @@ "title": "Minhas listas", "remove": "Remover", "empty_title": "Nada salvo ainda", - "empty_subtitle": "Os títulos que você salvar aparecem aqui." + "empty_subtitle": "Os títulos que você salvar aparecem aqui.", + "load_failed": "Não foi possível carregar esta lista." }, "torrent": { "title": "Torrents", @@ -2910,6 +2941,8 @@ "priming_title": "Nunca perca um episódio novo", "priming_body": "O Sozo avisa assim que algo que você segue ganha um episódio ou capítulo novo — e nada que você não tenha pedido.", "priming_body_title": "Saiba na hora quando “{}” ganhar um episódio novo — e nada que você não tenha pedido.", + "priming_download_title": "Saiba quando seu download terminar", + "priming_download_body": "O Sozo mostra o progresso do download nas notificações e avisa quando estiver pronto para assistir offline. Também pode avisar sobre novos episódios do que você segue.", "priming_point_releases": "Episódios novos, com o pôster", "priming_point_quiet": "Horário silencioso", "priming_point_control": "Silencie qualquer título", @@ -3138,7 +3171,8 @@ "error_attachment_count": "No máximo 5 capturas por mensagem.", "suggest": "Continua acontecendo? Fale com o suporte", "error_offline": "Você está offline ou a conexão caiu. Verifique a internet e tente novamente.", - "not_sent": "Não enviado: {}" + "not_sent": "Não enviado: {}", + "diag_crash_log": "Ver registro de falhas" }, "zangetsu": { "title": "Fontes Zangetsu", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 9bc900dd..2e068a8a 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -34,7 +34,9 @@ "remove": "Удалить", "show": "Показать", "hide": "Скрыть", - "installed": "Установлено" + "installed": "Установлено", + "load_more_failed": "Не удалось загрузить ещё", + "go_home": "На главную" }, "auth": { "login": "Войти", @@ -306,7 +308,11 @@ "no_source": "Для этой серии нет источника.", "empty_url": "Этот источник не вернул адрес видео.", "engine_unavailable": "Плеер не запустился. Полностью перезапустите приложение.", - "unknown": "Не удалось загрузить видео." + "unknown": "Не удалось загрузить видео.", + "not_found": "Этого видео больше нет на этом сервере. Выберите другой сервер.", + "server_error": "У видеосервера проблемы. Попробуйте чуть позже или выберите другой сервер.", + "resolve_failed": "Не удалось получить ссылку на видео из этого источника.", + "details": "Подробности" }, "play": "Воспроизвести", "pause": "Пауза", @@ -570,6 +576,8 @@ "subtitle_n": "Субтитры {}", "subtitle_in_video": "В самом видео", "rewind": "Назад", + "rewind_by": "{} сек назад", + "forward_by": "{} сек вперёд", "mini_restore": "Вернуть в большое окно", "pip_unavailable": "Не удалось включить «картинку в картинке» для этого видео.", "scrub_release_cancel": "Отпустите для отмены", @@ -585,7 +593,18 @@ "reaction_text_hint": "Коротко…", "reaction_wait": "Подождите несколько секунд перед следующей реакцией", "reaction_limit": "Лимит реакций для этой серии исчерпан", - "reaction_failed": "Не удалось отправить реакцию" + "reaction_failed": "Не удалось отправить реакцию", + "native_offer_title": "Попробовать новый плеер?", + "native_offer_body": "Кнопки и меню Liquid Glass от Apple. Это можно изменить в любой момент в Настройки → Плеер.", + "native_offer_try": "Попробовать", + "native_offer_not_now": "Не сейчас", + "trying_again": "Повторная попытка (попытка {})", + "try_mpv": "Попробовать с движком mpv", + "ok_to_skip": "OK — пропустить", + "quality_lowered": "Качество снижено", + "undo": "Отменить", + "crash_log": "Журнал сбоев", + "crash_log_empty": "Сбоев не зафиксировано" }, "profile": { "title": "Профиль", @@ -679,6 +698,15 @@ "player_engine_section": "ДВИЖОК ВОСПРОИЗВЕДЕНИЯ", "ask_engine_on_play": "Спрашивать каждый раз", "ask_engine_on_play_desc": "Выбирать плеер при запуске видео", + "controls_style": "СТИЛЬ КНОПОК ПЛЕЕРА", + "controls_style_desc": "Применится при следующем открытии плеера.", + "controls_style_native": "Системный (iOS 26)", + "controls_style_native_desc": "Кнопки, меню и окна Liquid Glass самого телефона", + "controls_style_system": "Системное стекло", + "controls_style_system_desc": "Кнопки приложения на Liquid Glass телефона", + "controls_style_classic": "Классическое стекло", + "controls_style_classic_desc": "Собственное стекло приложения, как в прежних версиях", + "controls_style_needs_ios26": "Нужна iOS 26 или новее", "playback_section": "ВОСПРОИЗВЕДЕНИЕ", "default_speed": "Скорость по умолчанию", "default_speed_desc": "С какой скоростью начинается видео", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "Свайп вверх и вниз слева", "volume_gesture": "Свайп громкости", "volume_gesture_desc": "Свайп вверх и вниз справа", + "auto_pip": "Автоматическая «картинка в картинке»", + "auto_pip_desc": "Если нажать «Домой» во время просмотра, видео продолжится в маленьком окне", "seconds_short": "{} с", "subtitle_style_note": "Действуют для всех видео. Тайминг субтитров задаётся отдельно для каждой серии в плеере.", "searching_all_sources": "поиск по всем источникам", @@ -2057,7 +2087,8 @@ "title": "Мои списки", "remove": "Удалить", "empty_title": "Пока ничего нет", - "empty_subtitle": "Сохранённое появится здесь." + "empty_subtitle": "Сохранённое появится здесь.", + "load_failed": "Не удалось загрузить список." }, "torrent": { "title": "Торренты", @@ -2911,6 +2942,8 @@ "priming_title": "Не пропускайте новые серии", "priming_body": "Sozo сообщит, как только у того, за чем вы следите, выйдет новая серия или глава, — и ничего сверх того, о чём вы просили.", "priming_body_title": "Узнайте первым, когда у «{}» выйдет новая серия, — и ничего лишнего.", + "priming_download_title": "Узнайте, когда загрузка завершится", + "priming_download_body": "Sozo показывает ход загрузки в уведомлениях и сообщает, когда можно смотреть офлайн. А ещё — о новых сериях того, за чем вы следите.", "priming_point_releases": "Новые серии с постером", "priming_point_quiet": "Тихие часы", "priming_point_control": "Отключение для любого тайтла", @@ -3139,7 +3172,8 @@ "error_attachment_count": "Не больше 5 скриншотов в сообщении.", "suggest": "Повторяется? Напишите в поддержку", "error_offline": "Нет интернета или соединение прервалось. Проверьте подключение и попробуйте снова.", - "not_sent": "Не отправлено: {}" + "not_sent": "Не отправлено: {}", + "diag_crash_log": "Открыть журнал сбоев" }, "zangetsu": { "title": "Источники Zangetsu", diff --git a/assets/translations/tr.json b/assets/translations/tr.json index 701b1e45..86029116 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -34,7 +34,9 @@ "remove": "Kaldır", "show": "Göster", "hide": "Gizle", - "installed": "Yüklendi" + "installed": "Yüklendi", + "load_more_failed": "Daha fazlası yüklenemedi", + "go_home": "Ana sayfaya dön" }, "auth": { "login": "Giriş yap", @@ -306,7 +308,11 @@ "no_source": "Bu bölüm için kaynak yok.", "empty_url": "Bu kaynak hiçbir video adresi döndürmedi.", "engine_unavailable": "Oynatıcı başlamadı. Lütfen uygulamayı tamamen yeniden başlat.", - "unknown": "Video yüklenemedi." + "unknown": "Video yüklenemedi.", + "not_found": "Bu video artık bu sunucuda yok. Başka bir sunucu deneyin.", + "server_error": "Video sunucusunda sorun var. Birazdan tekrar deneyin veya başka bir sunucu seçin.", + "resolve_failed": "Bu kaynaktan video bağlantısı alınamadı.", + "details": "Ayrıntılar" }, "play": "Oynat", "pause": "Duraklat", @@ -570,6 +576,8 @@ "subtitle_n": "Altyazı {}", "subtitle_in_video": "Videonun içinde", "rewind": "Geri sar", + "rewind_by": "{} sn geri", + "forward_by": "{} sn ileri", "mini_restore": "Tam pencereye dön", "pip_unavailable": "Bu video için resim içinde resim başlatılamadı.", "scrub_release_cancel": "İptal için bırak", @@ -585,7 +593,18 @@ "reaction_text_hint": "Kısa bir şey yaz…", "reaction_wait": "Tekrar tepki vermeden önce birkaç saniye bekle", "reaction_limit": "Bu bölüm için tepki sınırına ulaştın", - "reaction_failed": "Tepki gönderilemedi" + "reaction_failed": "Tepki gönderilemedi", + "native_offer_title": "Yeni oynatıcıyı denemek ister misiniz?", + "native_offer_body": "Apple’ın Liquid Glass kontrolleri ve menüleri. Bunu istediğiniz zaman Ayarlar → Oynatıcı bölümünden değiştirebilirsiniz.", + "native_offer_try": "Dene", + "native_offer_not_now": "Şimdi değil", + "trying_again": "Yeniden deneniyor ({}. deneme)", + "try_mpv": "mpv motoruyla dene", + "ok_to_skip": "Atlamak için OK", + "quality_lowered": "Kalite düşürüldü", + "undo": "Geri al", + "crash_log": "Çökme kaydı", + "crash_log_empty": "Kayıtlı çökme yok" }, "profile": { "title": "Profil", @@ -677,6 +696,15 @@ "player_engine_section": "OYNATMA MOTORU", "ask_engine_on_play": "Her seferinde sor", "ask_engine_on_play_desc": "Her video başlarken motoru seç", + "controls_style": "OYNATICI KONTROL STİLİ", + "controls_style_desc": "Oynatıcıyı bir sonraki açışınızda geçerli olur.", + "controls_style_native": "Yerel (iOS 26)", + "controls_style_native_desc": "Telefonun kendi Liquid Glass düğmeleri, menüleri ve sayfaları", + "controls_style_system": "Sistem camı", + "controls_style_system_desc": "Uygulamanın düğmeleri telefonun kendi Liquid Glass'ı üzerinde", + "controls_style_classic": "Klasik cam", + "controls_style_classic_desc": "Uygulamanın kendi camı, önceki sürümlerdeki gibi", + "controls_style_needs_ios26": "iOS 26 veya üstü gerekir", "playback_section": "OYNATMA", "default_speed": "Varsayılan hız", "default_speed_desc": "Her videonun başlayacağı hız", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "Sol tarafta yukarı ve aşağı kaydır", "volume_gesture": "Ses kaydırma", "volume_gesture_desc": "Sağ tarafta yukarı ve aşağı kaydır", + "auto_pip": "Otomatik resim içinde resim", + "auto_pip_desc": "Video oynarken Ana ekran tuşuna basınca küçük bir pencerede oynamaya devam eder", "seconds_short": "{} sn", "subtitle_style_note": "Bunlar her video için geçerlidir. Altyazı zamanlaması bölüm başına kalır ve oynatıcı içinde ayarlanır.", "searching_all_sources": "tüm kaynaklarda aranıyor", @@ -2056,7 +2086,8 @@ "title": "Listelerim", "remove": "Kaldır", "empty_title": "Henüz bir şey kaydedilmedi", - "empty_subtitle": "Kaydettiğin yapımlar burada görünür." + "empty_subtitle": "Kaydettiğin yapımlar burada görünür.", + "load_failed": "Bu liste yüklenemedi." }, "torrent": { "title": "Torrentler", @@ -2910,6 +2941,8 @@ "priming_title": "Hiçbir yeni bölümü kaçırma", "priming_body": "Takip ettiğin bir şeye yeni bölüm ya da kısım gelir gelmez Sozo haber verir — istemediğin hiçbir şey gelmez.", "priming_body_title": "“{}” yeni bölüm alır almaz haberin olsun — istemediğin hiçbir şey gelmez.", + "priming_download_title": "İndirmenin bittiğini öğren", + "priming_download_body": "Sozo indirme ilerlemesini bildirimlerde gösterir ve çevrimdışı izlemeye hazır olduğunda haber verir. Takip ettiklerinin yeni bölümlerini de bildirebilir.", "priming_point_releases": "Afişiyle yeni bölümler", "priming_point_quiet": "Sessiz saatler", "priming_point_control": "İstediğin yapımı sessize al", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Mesaj başına en fazla 5 ekran görüntüsü.", "suggest": "Tekrarlıyor mu? Desteğe yaz", "error_offline": "Çevrimdışısın veya bağlantı koptu. İnternetini kontrol edip tekrar dene.", - "not_sent": "Gönderilmedi: {}" + "not_sent": "Gönderilmedi: {}", + "diag_crash_log": "Çökme kaydını görüntüle" }, "zangetsu": { "title": "Zangetsu Kaynakları", diff --git a/assets/translations/uz.json b/assets/translations/uz.json index dddc90c9..7eeb8ab6 100644 --- a/assets/translations/uz.json +++ b/assets/translations/uz.json @@ -34,7 +34,9 @@ "remove": "Olib tashlash", "show": "Ko‘rsatish", "hide": "Yashirish", - "installed": "O‘rnatilgan" + "installed": "O‘rnatilgan", + "load_more_failed": "Ko‘proq yuklab bo‘lmadi", + "go_home": "Bosh sahifaga" }, "auth": { "login": "Kirish", @@ -306,7 +308,11 @@ "no_source": "Bu qism uchun manba topilmadi.", "empty_url": "Bu manba video manzilini qaytarmadi.", "engine_unavailable": "Pleyer ishga tushmadi. Ilovani butunlay qayta ishga tushiring.", - "unknown": "Videoni yuklab bo‘lmadi." + "unknown": "Videoni yuklab bo‘lmadi.", + "not_found": "Bu video endi ushbu serverda yo‘q. Boshqa serverni tanlang.", + "server_error": "Video serverida muammo bor. Birozdan so‘ng qayta urinib ko‘ring yoki boshqa serverni tanlang.", + "resolve_failed": "Bu manbadan video havolasini olib bo‘lmadi.", + "details": "Tafsilotlar" }, "play": "O'ynash", "pause": "To'xtatish", @@ -570,6 +576,8 @@ "subtitle_n": "Subtitr {}", "subtitle_in_video": "Videoning ichida", "rewind": "Orqaga", + "rewind_by": "{}s orqaga", + "forward_by": "{}s oldinga", "mini_restore": "Katta oynaga qaytish", "pip_unavailable": "Bu video uchun “rasm ichida rasm” ishga tushmadi.", "scrub_release_cancel": "Qo‘yib yuborsangiz bekor bo‘ladi", @@ -585,7 +593,18 @@ "reaction_text_hint": "Qisqa yozing…", "reaction_wait": "Qayta reaksiya bildirishdan oldin bir necha soniya kuting", "reaction_limit": "Bu qism uchun reaksiyalar limiti tugadi", - "reaction_failed": "Reaksiyani yuborib bo'lmadi" + "reaction_failed": "Reaksiyani yuborib bo'lmadi", + "native_offer_title": "Yangi pleyerni sinab ko‘rasizmi?", + "native_offer_body": "Apple’ning Liquid Glass tugmalari va menyulari. Buni istalgan vaqt Sozlamalar → Pleyer bo‘limida o‘zgartirishingiz mumkin.", + "native_offer_try": "Sinab ko‘rish", + "native_offer_not_now": "Hozir emas", + "trying_again": "Qayta urinilmoqda ({}-urinish)", + "try_mpv": "mpv dvigateli bilan sinash", + "ok_to_skip": "O‘tkazish uchun OK", + "quality_lowered": "Sifat pasaytirildi", + "undo": "Bekor qilish", + "crash_log": "Nosozliklar jurnali", + "crash_log_empty": "Nosozliklar qayd etilmagan" }, "profile": { "title": "Profil", @@ -679,6 +698,15 @@ "player_engine_section": "IJRO DVIGATELI", "ask_engine_on_play": "Har safar so‘ralsin", "ask_engine_on_play_desc": "Video boshlanganda pleyerni tanlash", + "controls_style": "PLEYER TUGMALARI USLUBI", + "controls_style_desc": "Pleyer keyingi safar ochilganda kuchga kiradi.", + "controls_style_native": "Tizimniki (iOS 26)", + "controls_style_native_desc": "Telefonning o'z Liquid Glass tugmalari, menyulari va oynalari", + "controls_style_system": "Tizim shishasi", + "controls_style_system_desc": "Ilova tugmalari telefonning o'z Liquid Glass shishasida", + "controls_style_classic": "Klassik shisha", + "controls_style_classic_desc": "Ilovaning o'z shishasi, avvalgi versiyalardagidek", + "controls_style_needs_ios26": "iOS 26 yoki yangisi kerak", "playback_section": "IJRO", "default_speed": "Standart tezlik", "default_speed_desc": "Har bir video shu tezlikda boshlanadi", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "Chap tomonda yuqoriga-pastga surish", "volume_gesture": "Ovoz surish", "volume_gesture_desc": "O‘ng tomonda yuqoriga-pastga surish", + "auto_pip": "Avtomatik «rasm ichida rasm»", + "auto_pip_desc": "Video ijro etilayotganda Home tugmasi bosilsa, u kichik oynada davom etadi", "seconds_short": "{} s", "subtitle_style_note": "Bular barcha videolarga tegishli. Subtitr vaqti esa har bir qism uchun alohida va pleyer ichida sozlanadi.", "searching_all_sources": "barcha manbalar bo‘yicha qidirilmoqda", @@ -2056,7 +2086,8 @@ "title": "Ro‘yxatlarim", "remove": "O‘chirish", "empty_title": "Hozircha hech narsa yo‘q", - "empty_subtitle": "Saqlaganlaringiz shu yerda ko‘rinadi." + "empty_subtitle": "Saqlaganlaringiz shu yerda ko‘rinadi.", + "load_failed": "Bu ro‘yxatni yuklab bo‘lmadi." }, "torrent": { "title": "Torrentlar", @@ -2910,6 +2941,8 @@ "priming_title": "Yangi qismni o‘tkazib yubormang", "priming_body": "Kuzatayotgan asaringizda yangi qism yoki bob chiqishi bilan Sozo xabar beradi — so‘ramagan narsangiz bilan bezovta qilmaydi.", "priming_body_title": "«{}»ning yangi qismi chiqishi bilan xabar oling — ortiqcha hech narsa yo‘q.", + "priming_download_title": "Yuklab olish tugaganini biling", + "priming_download_body": "Sozo yuklab olish jarayonini bildirishnomalarda ko‘rsatadi va oflayn ko‘rishga tayyor bo‘lganda xabar beradi. Kuzatayotganlaringizning yangi qismlari haqida ham aytib turadi.", "priming_point_releases": "Yangi qismlar, posteri bilan", "priming_point_quiet": "Sokin soatlar", "priming_point_control": "Istalgan asarni ovozsiz qilish", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Bitta xabarga ko‘pi bilan 5 ta skrinshot.", "suggest": "Takrorlanyaptimi? Support’ga yozing", "error_offline": "Internet yo'q yoki aloqa uzildi. Internetni tekshirib, qayta urinib ko'ring.", - "not_sent": "Yuborilmadi: {}" + "not_sent": "Yuborilmadi: {}", + "diag_crash_log": "Nosozliklar jurnalini ko‘rish" }, "zangetsu": { "title": "Zangetsu manbalari", diff --git a/assets/translations/vi.json b/assets/translations/vi.json index 54733568..c9acbd4f 100644 --- a/assets/translations/vi.json +++ b/assets/translations/vi.json @@ -34,7 +34,9 @@ "remove": "Gỡ", "show": "Hiện", "hide": "Ẩn", - "installed": "Đã cài" + "installed": "Đã cài", + "load_more_failed": "Không thể tải thêm", + "go_home": "Về trang chủ" }, "auth": { "login": "Đăng nhập", @@ -306,7 +308,11 @@ "no_source": "Tập này không có nguồn.", "empty_url": "Nguồn này không trả về địa chỉ video.", "engine_unavailable": "Trình phát không khởi động được. Vui lòng khởi động lại hẳn ứng dụng.", - "unknown": "Không tải được video." + "unknown": "Không tải được video.", + "not_found": "Video này không còn trên máy chủ này. Hãy thử máy chủ khác.", + "server_error": "Máy chủ video đang gặp sự cố. Hãy thử lại sau giây lát hoặc chọn máy chủ khác.", + "resolve_failed": "Không lấy được liên kết video từ nguồn này.", + "details": "Chi tiết" }, "play": "Phát", "pause": "Tạm dừng", @@ -570,6 +576,8 @@ "subtitle_n": "Phụ đề {}", "subtitle_in_video": "Trong video", "rewind": "Tua lại", + "rewind_by": "Tua lại {} giây", + "forward_by": "Tua tới {} giây", "mini_restore": "Trở về cửa sổ đầy đủ", "pip_unavailable": "Không thể bật hình trong hình cho video này.", "scrub_release_cancel": "Thả ra để hủy", @@ -585,7 +593,18 @@ "reaction_text_hint": "Viết ngắn thôi…", "reaction_wait": "Chờ vài giây trước khi bày tỏ tiếp", "reaction_limit": "Bạn đã đạt giới hạn cảm xúc cho tập này", - "reaction_failed": "Không gửi được cảm xúc" + "reaction_failed": "Không gửi được cảm xúc", + "native_offer_title": "Dùng thử trình phát mới?", + "native_offer_body": "Nút điều khiển và menu Liquid Glass của Apple. Bạn có thể thay đổi bất cứ lúc nào trong Cài đặt → Trình phát.", + "native_offer_try": "Dùng thử", + "native_offer_not_now": "Để sau", + "trying_again": "Đang thử lại (lần {})", + "try_mpv": "Thử với trình phát mpv", + "ok_to_skip": "Nhấn OK để bỏ qua", + "quality_lowered": "Đã giảm chất lượng", + "undo": "Hoàn tác", + "crash_log": "Nhật ký sự cố", + "crash_log_empty": "Chưa ghi nhận sự cố nào" }, "profile": { "title": "Hồ sơ", @@ -679,6 +698,15 @@ "player_engine_section": "CÔNG CỤ PHÁT", "ask_engine_on_play": "Luôn hỏi", "ask_engine_on_play_desc": "Chọn công cụ phát mỗi khi bắt đầu video", + "controls_style": "KIỂU NÚT ĐIỀU KHIỂN TRÌNH PHÁT", + "controls_style_desc": "Áp dụng từ lần mở trình phát tiếp theo.", + "controls_style_native": "Gốc (iOS 26)", + "controls_style_native_desc": "Nút, menu và bảng Liquid Glass của chính điện thoại", + "controls_style_system": "Kính hệ thống", + "controls_style_system_desc": "Nút của ứng dụng trên Liquid Glass của điện thoại", + "controls_style_classic": "Kính cổ điển", + "controls_style_classic_desc": "Lớp kính riêng của ứng dụng, như các phiên bản trước", + "controls_style_needs_ios26": "Cần iOS 26 trở lên", "playback_section": "PHÁT LẠI", "default_speed": "Tốc độ mặc định", "default_speed_desc": "Tốc độ khi bắt đầu mỗi video", @@ -701,6 +729,8 @@ "brightness_gesture_desc": "Vuốt lên xuống ở bên trái", "volume_gesture": "Vuốt chỉnh âm lượng", "volume_gesture_desc": "Vuốt lên xuống ở bên phải", + "auto_pip": "Tự động hình trong hình", + "auto_pip_desc": "Nhấn Màn hình chính khi đang phát video sẽ tiếp tục phát trong cửa sổ nhỏ", "seconds_short": "{} giây", "subtitle_style_note": "Áp dụng cho mọi video. Thời gian phụ đề được đặt riêng cho từng tập ngay trong trình phát.", "searching_all_sources": "đang tìm mọi nguồn", @@ -2056,7 +2086,8 @@ "title": "Danh sách của tôi", "remove": "Xóa", "empty_title": "Chưa lưu gì", - "empty_subtitle": "Các phim bạn lưu sẽ hiện ở đây." + "empty_subtitle": "Các phim bạn lưu sẽ hiện ở đây.", + "load_failed": "Không thể tải danh sách này." }, "torrent": { "title": "Torrent", @@ -2910,6 +2941,8 @@ "priming_title": "Không bỏ lỡ tập mới nào", "priming_body": "Sozo báo ngay khi nội dung bạn theo dõi có tập hoặc chương mới — và không gửi gì bạn chưa yêu cầu.", "priming_body_title": "Nhận thông báo ngay khi {} có tập mới — và không gửi gì bạn chưa yêu cầu.", + "priming_download_title": "Biết khi nào tải xuống hoàn tất", + "priming_download_body": "Sozo hiển thị tiến trình tải xuống trong thông báo và báo cho bạn khi có thể xem ngoại tuyến. Sozo cũng có thể báo tập mới của những gì bạn theo dõi.", "priming_point_releases": "Tập mới, kèm áp phích", "priming_point_quiet": "Giờ yên tĩnh", "priming_point_control": "Tắt tiếng từng phim", @@ -3138,7 +3171,8 @@ "error_attachment_count": "Tối đa 5 ảnh mỗi tin nhắn.", "suggest": "Vẫn tiếp diễn? Hãy viết cho bộ phận hỗ trợ", "error_offline": "Bạn đang ngoại tuyến hoặc kết nối bị ngắt. Hãy kiểm tra internet rồi thử lại.", - "not_sent": "Chưa gửi được: {}" + "not_sent": "Chưa gửi được: {}", + "diag_crash_log": "Xem nhật ký sự cố" }, "zangetsu": { "title": "Nguồn Zangetsu", diff --git a/assets/translations/yue.json b/assets/translations/yue.json index 531b9e88..633e60af 100644 --- a/assets/translations/yue.json +++ b/assets/translations/yue.json @@ -34,7 +34,9 @@ "remove": "移除", "show": "顯示", "hide": "隱藏", - "installed": "已安裝" + "installed": "已安裝", + "load_more_failed": "載入唔到更多", + "go_home": "返去主頁" }, "auth": { "login": "登入", @@ -306,7 +308,11 @@ "no_source": "呢一集冇來源。", "empty_url": "呢個來源冇畀到影片地址。", "engine_unavailable": "播放器啟動唔到。請完全重開個 app。", - "unknown": "載入唔到影片。" + "unknown": "載入唔到影片。", + "not_found": "呢個伺服器已經冇咗呢段影片,試吓第二個伺服器。", + "server_error": "影片伺服器出咗問題,等陣再試或者揀第二個伺服器。", + "resolve_failed": "喺呢個來源攞唔到影片連結。", + "details": "詳情" }, "play": "播放", "pause": "暫停", @@ -570,6 +576,8 @@ "subtitle_n": "字幕 {}", "subtitle_in_video": "影片內置", "rewind": "倒帶", + "rewind_by": "倒帶 {} 秒", + "forward_by": "快進 {} 秒", "mini_restore": "返去大視窗", "pip_unavailable": "呢條片開唔到子母畫面。", "scrub_release_cancel": "鬆手就取消", @@ -585,7 +593,18 @@ "reaction_text_hint": "講句簡短嘅…", "reaction_wait": "等幾秒先再反應", "reaction_limit": "呢集嘅反應已經到上限", - "reaction_failed": "送唔到個反應" + "reaction_failed": "送唔到個反應", + "native_offer_title": "試吓新播放器?", + "native_offer_body": "Apple 嘅 Liquid Glass 控制同選單。你隨時可以喺 設定 → 播放器 度改返。", + "native_offer_try": "試吓", + "native_offer_not_now": "遲啲先", + "trying_again": "再試緊(第 {} 次)", + "try_mpv": "用 mpv 引擎試吓", + "ok_to_skip": "撳 OK 跳過", + "quality_lowered": "已降低畫質", + "undo": "復原", + "crash_log": "當機紀錄", + "crash_log_empty": "冇當機紀錄" }, "profile": { "title": "個人檔案", @@ -677,6 +696,15 @@ "player_engine_section": "播放引擎", "ask_engine_on_play": "每次都問", "ask_engine_on_play_desc": "每次開片都揀一次引擎", + "controls_style": "播放器控制樣式", + "controls_style_desc": "下次打開播放器時生效。", + "controls_style_native": "原生(iOS 26)", + "controls_style_native_desc": "手機自己嘅 Liquid Glass 按鈕、選單同面板", + "controls_style_system": "系統玻璃", + "controls_style_system_desc": "App 嘅按鈕放喺手機自己嘅 Liquid Glass 上面", + "controls_style_classic": "經典玻璃", + "controls_style_classic_desc": "App 自己嘅玻璃,同舊版本一樣", + "controls_style_needs_ios26": "需要 iOS 26 或更新版本", "playback_section": "播放", "default_speed": "預設速度", "default_speed_desc": "每條片開始時嘅速度", @@ -699,6 +727,8 @@ "brightness_gesture_desc": "喺左邊上下掃", "volume_gesture": "音量滑動", "volume_gesture_desc": "喺右邊上下掃", + "auto_pip": "自動畫中畫", + "auto_pip_desc": "播放影片時撳主畫面,影片會喺細視窗繼續播", "seconds_short": "{}秒", "subtitle_style_note": "呢啲設定套用喺所有影片。字幕時間就每集獨立,要喺播放器入面調。", "searching_all_sources": "搜尋緊所有來源", @@ -2056,7 +2086,8 @@ "title": "我嘅清單", "remove": "移除", "empty_title": "仲未儲存任何嘢", - "empty_subtitle": "你儲存咗嘅作品會喺呢度出現。" + "empty_subtitle": "你儲存咗嘅作品會喺呢度出現。", + "load_failed": "載入唔到呢個清單。" }, "torrent": { "title": "Torrent", @@ -2910,6 +2941,8 @@ "priming_title": "唔好再錯過新一集", "priming_body": "你追緊嘅作品一出新一集或者新一話,Sozo 就即刻話你知 — 你冇要求嘅就唔會煩你。", "priming_body_title": "「{}」一出新一集就即刻知 — 你冇要求嘅就唔會煩你。", + "priming_download_title": "下載完成即刻知", + "priming_download_body": "Sozo 會喺通知度顯示下載進度,可以離線睇嘅時候話你知。亦都可以通知你追緊嘅劇有新一集。", "priming_point_releases": "新一集連海報", "priming_point_quiet": "靜音時段", "priming_point_control": "任何作品都可以靜音", @@ -3138,7 +3171,8 @@ "error_attachment_count": "每條訊息最多 5 張截圖。", "suggest": "成日都係咁?搵支援", "error_offline": "你而家離線,或者連線斷咗。請檢查網絡再試。", - "not_sent": "未送出:{}" + "not_sent": "未送出:{}", + "diag_crash_log": "睇當機紀錄" }, "zangetsu": { "title": "Zangetsu 來源", diff --git a/deeplink/README.md b/deeplink/README.md index 7b2771fb..49cef83f 100644 --- a/deeplink/README.md +++ b/deeplink/README.md @@ -85,3 +85,21 @@ So'ng `sozo.azamov.me` DNS yozuvini backend'ning IP'siga yo'naltirish kerak `detail.html` ichida iOS App Store URL'i hozircha `id0000000000` — haqiqiy `id...` ni ilova chiqqach almashtiring. + +## `/play` links (for sozo-web) + +The app now reads these parameters on `/play` (and `/detail`): + +| Param | Meaning | +|---|---| +| `url` | content URL (required) | +| `provider` | source id; used for this navigation only, never saved as the current source | +| `ep` | episode number as shown to the viewer (1-based), `/play` only | +| `t` | start position in seconds (`95`, `1:35` and `1m35s` are all accepted), `/play` only | + +Example: `https://sozo.azamov.me/play?url=…&provider=hdrezka&ep=12&t=754` + +The web fallback page for `/play` (and `/detail`) must forward the **whole** +query string to `sozo://play…`, not only `url`. `detail.html` in this folder +does that now; the copy deployed by the sozo-web repo needs the same change. +`apple-app-site-association` already lists `/play` and `/play/*`. diff --git a/deeplink/detail.html b/deeplink/detail.html index 0c354880..69da7935 100644 --- a/deeplink/detail.html +++ b/deeplink/detail.html @@ -34,10 +34,9 @@

Sozo'da oching