diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 63969897..cd2f144d 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -23,6 +23,7 @@
+
diff --git a/README.md b/README.md
index 3e22b7cb..59487ae1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,12 @@
# M3UAndroid

+
+### 📢 Translations Wanted 📢
+
+Please submit a pull request if you want to help with translation.
+
+App strings ([ENGLISH](i18n/src/main/res/values), [Simplified Chinese](i18n/src/main/res/values-zh-rCN))
+
### Features
- [x] M3U and M3U8 files.
diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
index 7a4cdcfc..9d31bfe5 100644
--- a/androidApp/src/main/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -28,7 +28,7 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
+ android:label="@string/ui_app_name"
android:supportsRtl="true"
android:theme="@style/Theme.M3U.Starting"
android:usesCleartextTraffic="true"
@@ -54,7 +54,7 @@
helper.title = feed.title.ifEmpty {
- if (feed.local) context.getString(R.string.imported_feed_title)
+ if (feed.local) context.getString(I18R.string.feat_main_imported_feed_title)
else ""
}
navigate(Destination.Feed(feed.url))
diff --git a/androidApp/src/main/res/values-zh-rCN/strings.xml b/androidApp/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index d7a47c49..00000000
--- a/androidApp/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
- 应用发生崩溃
- 导入频道
-
\ No newline at end of file
diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml
deleted file mode 100644
index 77de108e..00000000
--- a/androidApp/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- app crashes
- Import as live
-
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index c9e613d7..ac3e3fae 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -35,6 +35,7 @@ android {
dependencies {
lintPublish(project(":lint"))
lintChecks(project(":lint"))
+ api(project(":i18n"))
implementation(libs.androidx.core.core.ktx)
implementation(libs.androidx.appcompat.appcompat)
diff --git a/data/src/main/java/com/m3u/data/repository/impl/FeedRepositoryImpl.kt b/data/src/main/java/com/m3u/data/repository/impl/FeedRepositoryImpl.kt
index 1dd6c5fb..f1a8efd7 100644
--- a/data/src/main/java/com/m3u/data/repository/impl/FeedRepositoryImpl.kt
+++ b/data/src/main/java/com/m3u/data/repository/impl/FeedRepositoryImpl.kt
@@ -15,7 +15,6 @@ import com.m3u.core.wrapper.emitException
import com.m3u.core.wrapper.emitMessage
import com.m3u.core.wrapper.emitProgress
import com.m3u.core.wrapper.emitResource
-import com.m3u.data.R
import com.m3u.data.database.dao.FeedDao
import com.m3u.data.database.dao.LiveDao
import com.m3u.data.database.entity.Feed
@@ -35,6 +34,7 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import javax.inject.Inject
+import com.m3u.i18n.R as I18R
class FeedRepositoryImpl @Inject constructor(
private val feedDao: FeedDao,
@@ -124,7 +124,7 @@ class FeedRepositoryImpl @Inject constructor(
liveDao.insert(live)
emitResource(Unit)
} catch (e: FileNotFoundException) {
- error(context.getString(R.string.error_file_not_found))
+ error(context.getString(I18R.string.data_error_file_not_found))
} catch (e: Exception) {
logger.log(e)
emitException(e)
diff --git a/data/src/main/java/com/m3u/data/worker/SubscriptionInBackgroundWorker.kt b/data/src/main/java/com/m3u/data/worker/SubscriptionInBackgroundWorker.kt
index 12a84705..bf09c633 100644
--- a/data/src/main/java/com/m3u/data/worker/SubscriptionInBackgroundWorker.kt
+++ b/data/src/main/java/com/m3u/data/worker/SubscriptionInBackgroundWorker.kt
@@ -17,6 +17,7 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.supervisorScope
+import com.m3u.i18n.R as I18R
@HiltWorker
class SubscriptionInBackgroundWorker @AssistedInject constructor(
@@ -33,7 +34,7 @@ class SubscriptionInBackgroundWorker @AssistedInject constructor(
url ?: return@coroutineScope Result.failure()
createChannel()
if (title.isEmpty()) {
- val message = context.getString(R.string.error_empty_title)
+ val message = context.getString(I18R.string.data_error_empty_title)
val data = workDataOf("message" to message)
failure(message)
Result.failure(data)
diff --git a/data/src/main/res/values-zh-rCN/strings.xml b/data/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 07782a36..00000000
--- a/data/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 文件不存在
- 订阅名称为空
-
\ No newline at end of file
diff --git a/data/src/main/res/values/strings.xml b/data/src/main/res/values/strings.xml
deleted file mode 100644
index db54023d..00000000
--- a/data/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- file not found
- playlist name is empty
-
\ No newline at end of file
diff --git a/features/about/src/main/java/com/m3u/features/about/AboutScreen.kt b/features/about/src/main/java/com/m3u/features/about/AboutScreen.kt
index 22b5a624..1b8222ed 100644
--- a/features/about/src/main/java/com/m3u/features/about/AboutScreen.kt
+++ b/features/about/src/main/java/com/m3u/features/about/AboutScreen.kt
@@ -19,6 +19,7 @@ import com.m3u.ui.components.MonoText
import com.m3u.ui.model.LocalHelper
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.repeatOnLifecycle
+import com.m3u.i18n.R as I18R
@Composable
internal fun AboutRoute(
@@ -26,7 +27,7 @@ internal fun AboutRoute(
viewModel: AboutViewModel = hiltViewModel()
) {
val helper = LocalHelper.current
- val title = stringResource(R.string.about_title)
+ val title = stringResource(I18R.string.feat_about_title)
helper.repeatOnLifecycle {
this.title = title
}
diff --git a/features/about/src/main/res/values-zh-rCN/strings.xml b/features/about/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index d44e9bf7..00000000
--- a/features/about/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- 关于项目
-
\ No newline at end of file
diff --git a/features/about/src/main/res/values/strings.xml b/features/about/src/main/res/values/strings.xml
deleted file mode 100644
index 750b94b4..00000000
--- a/features/about/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- about project
-
\ No newline at end of file
diff --git a/features/console/src/main/java/com/m3u/features/console/ConsoleScreen.kt b/features/console/src/main/java/com/m3u/features/console/ConsoleScreen.kt
index c61e29ad..4ebddde0 100644
--- a/features/console/src/main/java/com/m3u/features/console/ConsoleScreen.kt
+++ b/features/console/src/main/java/com/m3u/features/console/ConsoleScreen.kt
@@ -30,6 +30,7 @@ import com.m3u.ui.components.TextField
import com.m3u.ui.model.LocalHelper
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.repeatOnLifecycle
+import com.m3u.i18n.R as I18R
@Composable
internal fun ConsoleRoute(
@@ -37,7 +38,7 @@ internal fun ConsoleRoute(
viewModel: ConsoleViewModel = hiltViewModel()
) {
val helper = LocalHelper.current
- val title = stringResource(R.string.console_title)
+ val title = stringResource(I18R.string.feat_console_title)
helper.repeatOnLifecycle {
this.title = title
}
diff --git a/features/console/src/main/res/values-zh-rCN/strings.xml b/features/console/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 0caecff2..00000000
--- a/features/console/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- Console Editor
-
\ No newline at end of file
diff --git a/features/console/src/main/res/values/strings.xml b/features/console/src/main/res/values/strings.xml
deleted file mode 100644
index 0caecff2..00000000
--- a/features/console/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- Console Editor
-
\ No newline at end of file
diff --git a/features/favorite/src/main/java/com/m3u/features/favorite/components/FavoriteLiveItem.kt b/features/favorite/src/main/java/com/m3u/features/favorite/components/FavoriteLiveItem.kt
index 1a310125..b2bcae2b 100644
--- a/features/favorite/src/main/java/com/m3u/features/favorite/components/FavoriteLiveItem.kt
+++ b/features/favorite/src/main/java/com/m3u/features/favorite/components/FavoriteLiveItem.kt
@@ -25,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import com.m3u.data.database.entity.Live
-import com.m3u.features.favorite.R
import com.m3u.ui.components.Image
import com.m3u.ui.components.TextBadge
import com.m3u.ui.model.LocalScalable
@@ -33,6 +32,7 @@ import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
import com.m3u.ui.ktx.animated
import java.net.URI
+import com.m3u.i18n.R as I18R
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -52,7 +52,7 @@ internal fun FavoriteItem(
val actualBackgroundColor by theme.surface.animated("FavoriteItemBackground")
val actualContentColor by theme.onSurface.animated("FavoriteItemContent")
val scheme = remember(live) {
- URI(live.url).scheme ?: context.getString(R.string.scheme_unknown).uppercase()
+ URI(live.url).scheme ?: context.getString(I18R.string.feat_feed_scheme_unknown).uppercase()
}
Surface(
shape = RoundedCornerShape(spacing.medium),
diff --git a/features/favorite/src/main/res/values-zh-rCN/strings.xml b/features/favorite/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 9579423a..00000000
--- a/features/favorite/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- 未知
-
\ No newline at end of file
diff --git a/features/favorite/src/main/res/values/strings.xml b/features/favorite/src/main/res/values/strings.xml
deleted file mode 100644
index 974f4be6..00000000
--- a/features/favorite/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- unknown
-
\ No newline at end of file
diff --git a/features/feed/src/main/java/com/m3u/features/feed/FeedScreen.kt b/features/feed/src/main/java/com/m3u/features/feed/FeedScreen.kt
index df435823..5768f3b8 100644
--- a/features/feed/src/main/java/com/m3u/features/feed/FeedScreen.kt
+++ b/features/feed/src/main/java/com/m3u/features/feed/FeedScreen.kt
@@ -94,6 +94,7 @@ import com.m3u.ui.model.Scalable
import com.m3u.ui.model.repeatOnLifecycle
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import com.m3u.i18n.R as I18R
internal typealias NavigateToLive = (liveId: Int) -> Unit
internal typealias NavigateToPlaylist = (playlist: List, initial: Int) -> Unit
@@ -259,7 +260,7 @@ private fun FeedScreen(
onValueChange = onQuery,
fontWeight = FontWeight.Bold,
height = 32.dp,
- placeholder = stringResource(R.string.query_placeholder).capitalize(Locale.current),
+ placeholder = stringResource(I18R.string.feat_feed_query_placeholder).capitalize(Locale.current),
modifier = Modifier
.padding(spacing.medium)
.fillMaxWidth()
diff --git a/features/feed/src/main/java/com/m3u/features/feed/FeedViewModel.kt b/features/feed/src/main/java/com/m3u/features/feed/FeedViewModel.kt
index 179f32ca..b0c3d45a 100644
--- a/features/feed/src/main/java/com/m3u/features/feed/FeedViewModel.kt
+++ b/features/feed/src/main/java/com/m3u/features/feed/FeedViewModel.kt
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
+import com.m3u.i18n.R as I18R
@HiltViewModel
class FeedViewModel @Inject constructor(
@@ -54,7 +55,7 @@ class FeedViewModel @Inject constructor(
private fun observe(feedUrl: String) {
observeJob?.cancel()
if (feedUrl.isEmpty()) {
- val message = string(R.string.error_observe_feed, "")
+ val message = string(I18R.string.feat_feed_error_observe_feed, "")
onMessage(message)
return
}
@@ -74,7 +75,7 @@ class FeedViewModel @Inject constructor(
)
}
} else {
- val message = string(R.string.error_observe_feed, feedUrl)
+ val message = string(I18R.string.feat_feed_error_observe_feed, feedUrl)
onMessage(message)
}
}
diff --git a/features/feed/src/main/java/com/m3u/features/feed/components/FeedDialog.kt b/features/feed/src/main/java/com/m3u/features/feed/components/FeedDialog.kt
index 049139cf..6d522858 100644
--- a/features/feed/src/main/java/com/m3u/features/feed/components/FeedDialog.kt
+++ b/features/feed/src/main/java/com/m3u/features/feed/components/FeedDialog.kt
@@ -14,11 +14,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import com.m3u.data.database.entity.Live
-import com.m3u.features.feed.R
import com.m3u.ui.components.DialogTextField
import com.m3u.ui.components.DialogItem
import com.m3u.ui.components.AppDialog
import com.m3u.ui.model.LocalSpacing
+import com.m3u.i18n.R as I18R
internal typealias OnUpdateDialogStatus = (DialogStatus) -> Unit
internal typealias OnFavoriteLive = (liveId: Int, target: Boolean) -> Unit
@@ -48,18 +48,18 @@ internal fun FeedDialog(
)
val favourite = status.live.favourite
DialogItem(
- if (favourite) R.string.dialog_favourite_cancel_title
- else R.string.dialog_favourite_title
+ if (favourite) I18R.string.feat_feed_dialog_favourite_cancel_title
+ else I18R.string.feat_feed_dialog_favourite_title
) {
onUpdate(DialogStatus.Idle)
onFavorite(status.live.id, !favourite)
}
- DialogItem(R.string.dialog_mute_title) {
+ DialogItem(I18R.string.feat_feed_dialog_mute_title) {
onUpdate(DialogStatus.Idle)
onBanned(status.live.id, true)
}
if (!status.live.cover.isNullOrEmpty()) {
- DialogItem(R.string.dialog_save_picture_title) {
+ DialogItem(I18R.string.feat_feed_dialog_save_picture_title) {
onUpdate(DialogStatus.Idle)
onSavePicture(status.live.id)
}
diff --git a/features/feed/src/main/java/com/m3u/features/feed/components/LiveItem.kt b/features/feed/src/main/java/com/m3u/features/feed/components/LiveItem.kt
index 1bb4a775..ec4c0668 100644
--- a/features/feed/src/main/java/com/m3u/features/feed/components/LiveItem.kt
+++ b/features/feed/src/main/java/com/m3u/features/feed/components/LiveItem.kt
@@ -28,13 +28,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import com.m3u.data.database.entity.Live
-import com.m3u.features.feed.R
import com.m3u.ui.components.Image
import com.m3u.ui.components.TextBadge
import com.m3u.ui.model.LocalScalable
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
import java.net.URI
+import com.m3u.i18n.R as I18R
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -57,7 +57,7 @@ internal fun LiveItem(
URI(live.url).scheme
} catch (ignored: Exception) {
null
- } ?: context.getString(R.string.scheme_unknown).uppercase()
+ } ?: context.getString(I18R.string.feat_feed_scheme_unknown).uppercase()
}
Surface(
shape = RoundedCornerShape(spacing.medium),
diff --git a/features/feed/src/main/res/values-zh-rCN/strings.xml b/features/feed/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 53e600df..00000000
--- a/features/feed/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 未知
- 屏蔽这个频道?
-
- 喜欢
- 取消喜欢
- 屏蔽
-
- 订阅不存在(%s)
- 保存封面
- 输入关键字
-
\ No newline at end of file
diff --git a/features/feed/src/main/res/values/strings.xml b/features/feed/src/main/res/values/strings.xml
deleted file mode 100644
index b1fb12bc..00000000
--- a/features/feed/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- unknown
- do you want to ban this live?
- like
- cancel like
- ban
- playlist is not existed (%s)
- save to gallery
- enter key word
-
\ No newline at end of file
diff --git a/features/live/src/main/java/com/m3u/features/live/components/DlnaDevicesBottomSheet.kt b/features/live/src/main/java/com/m3u/features/live/components/DlnaDevicesBottomSheet.kt
index 2b870ea2..f79d81bc 100644
--- a/features/live/src/main/java/com/m3u/features/live/components/DlnaDevicesBottomSheet.kt
+++ b/features/live/src/main/java/com/m3u/features/live/components/DlnaDevicesBottomSheet.kt
@@ -24,12 +24,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
-import com.m3u.features.live.R
import com.m3u.ui.components.CircularProgressIndicator
import com.m3u.ui.components.MaskState
import com.m3u.ui.components.OnDismiss
import com.m3u.ui.model.LocalSpacing
import net.mm2d.upnp.Device
+import com.m3u.i18n.R as I18R
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
@@ -67,7 +67,7 @@ fun DlnaDevicesBottomSheet(
)
) {
Text(
- text = stringResource(R.string.dlna_devices),
+ text = stringResource(I18R.string.feat_live_dlna_devices),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.h6,
modifier = Modifier.weight(1f)
diff --git a/features/live/src/main/java/com/m3u/features/live/fragments/LiveFragment.kt b/features/live/src/main/java/com/m3u/features/live/fragments/LiveFragment.kt
index b455786d..c8ce913a 100644
--- a/features/live/src/main/java/com/m3u/features/live/fragments/LiveFragment.kt
+++ b/features/live/src/main/java/com/m3u/features/live/fragments/LiveFragment.kt
@@ -39,7 +39,6 @@ import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import com.m3u.core.annotation.ClipMode
import com.m3u.core.util.basic.isNotEmpty
-import com.m3u.features.live.R
import com.m3u.features.live.components.CoverPlaceholder
import com.m3u.features.live.components.LiveMask
import com.m3u.ui.components.Background
@@ -52,6 +51,7 @@ import com.m3u.ui.components.rememberPlayerState
import com.m3u.ui.model.LocalHelper
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
+import com.m3u.i18n.R as I18R
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -114,7 +114,7 @@ internal fun LiveFragment(
state = maskState,
icon = Icons.AutoMirrored.Rounded.ArrowBack,
onClick = onBackPressed,
- contentDescription = stringResource(R.string.tooltip_on_back_pressed)
+ contentDescription = stringResource(I18R.string.feat_live_tooltip_on_back_pressed)
)
Spacer(modifier = Modifier.weight(1f))
MaskButton(
@@ -122,16 +122,16 @@ internal fun LiveFragment(
icon = if (muted) Icons.AutoMirrored.Rounded.VolumeMute
else Icons.AutoMirrored.Rounded.VolumeUp,
onClick = onMuted,
- contentDescription = if (muted) stringResource(R.string.tooltip_unmute)
- else stringResource(R.string.tooltip_mute)
+ contentDescription = if (muted) stringResource(I18R.string.feat_live_tooltip_unmute)
+ else stringResource(I18R.string.feat_live_tooltip_mute)
)
MaskButton(
state = maskState,
icon = Icons.Rounded.Star,
tint = if (stared) Color.Yellow else Color.Unspecified,
onClick = onFavourite,
- contentDescription = if (stared) stringResource(R.string.tooltip_unfavourite)
- else stringResource(R.string.tooltip_favourite)
+ contentDescription = if (stared) stringResource(I18R.string.feat_live_tooltip_unfavourite)
+ else stringResource(I18R.string.feat_live_tooltip_favourite)
)
if (experimentalMode) {
MaskButton(
@@ -142,8 +142,8 @@ internal fun LiveFragment(
tint = if (recording) LocalTheme.current.error
else Color.Unspecified,
onClick = onRecord,
- contentDescription = if (recording) stringResource(R.string.tooltip_unrecord)
- else stringResource(R.string.tooltip_record)
+ contentDescription = if (recording) stringResource(I18R.string.feat_live_tooltip_unrecord)
+ else stringResource(I18R.string.feat_live_tooltip_record)
)
if (playback != Player.STATE_IDLE) {
MaskButton(
@@ -151,7 +151,7 @@ internal fun LiveFragment(
enabled = false,
icon = Icons.Rounded.Cast,
onClick = searchDlnaDevices,
- contentDescription = stringResource(R.string.tooltip_cast)
+ contentDescription = stringResource(I18R.string.feat_live_tooltip_cast)
)
}
}
@@ -163,7 +163,7 @@ internal fun LiveFragment(
helper.enterPipMode(videoSize)
maskState.sleep()
},
- contentDescription = stringResource(R.string.tooltip_enter_pip_mode)
+ contentDescription = stringResource(I18R.string.feat_live_tooltip_enter_pip_mode)
)
}
},
@@ -255,10 +255,10 @@ private val PlaybackException?.displayText: String
private val @Player.State Int.displayText: String
@Composable get() = when (this) {
- Player.STATE_IDLE -> R.string.playback_state_idle
- Player.STATE_BUFFERING -> R.string.playback_state_buffering
+ Player.STATE_IDLE -> I18R.string.feat_live_playback_state_idle
+ Player.STATE_BUFFERING -> I18R.string.feat_live_playback_state_buffering
Player.STATE_READY -> null
- Player.STATE_ENDED -> R.string.playback_state_ended
+ Player.STATE_ENDED -> I18R.string.feat_live_playback_state_ended
else -> null
}
?.let { stringResource(it) }
diff --git a/features/live/src/main/res/values-zh-rCN/strings.xml b/features/live/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index bc111ed4..00000000
--- a/features/live/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- 空闲
- 缓冲中
- 就绪
- 结束
-
- 返回
- 静音
- 取消静音
- 收藏
- 取消收藏
- 录制
- 停止录制
- 投屏
- 画中画模式
- DLNA 设备
-
\ No newline at end of file
diff --git a/features/live/src/main/res/values/strings.xml b/features/live/src/main/res/values/strings.xml
deleted file mode 100644
index ef7456af..00000000
--- a/features/live/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- idle
- buffering
- ready
- completed
-
- back
- mute
- unmute
- favourite
- unfavourite
- record
- unrecord
- cast screen
- PIP mode
- DLNA Devices
-
\ No newline at end of file
diff --git a/features/main/src/main/java/com/m3u/features/main/MainScreen.kt b/features/main/src/main/java/com/m3u/features/main/MainScreen.kt
index 429f2a1c..04690cba 100644
--- a/features/main/src/main/java/com/m3u/features/main/MainScreen.kt
+++ b/features/main/src/main/java/com/m3u/features/main/MainScreen.kt
@@ -36,6 +36,7 @@ import com.m3u.ui.model.LocalHelper
import com.m3u.ui.model.LocalScalable
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.Scalable
+import com.m3u.i18n.R as I18R
private typealias showDialog = (Feed) -> Unit
typealias NavigateToFeed = (feed: Feed) -> Unit
@@ -213,7 +214,7 @@ private fun LandscapeOrientationContent(
@Composable
private fun Feed.calculateUiTitle(): AnnotatedString {
val actual = title.ifEmpty {
- if (local) stringResource(R.string.imported_feed_title)
+ if (local) stringResource(I18R.string.feat_main_imported_feed_title)
else ""
}
return AnnotatedString(
diff --git a/features/main/src/main/java/com/m3u/features/main/MainViewModel.kt b/features/main/src/main/java/com/m3u/features/main/MainViewModel.kt
index 288189b1..b021d48c 100644
--- a/features/main/src/main/java/com/m3u/features/main/MainViewModel.kt
+++ b/features/main/src/main/java/com/m3u/features/main/MainViewModel.kt
@@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
+import com.m3u.i18n.R as I18R
@HiltViewModel
class MainViewModel @Inject constructor(
@@ -70,7 +71,7 @@ class MainViewModel @Inject constructor(
viewModelScope.launch {
val feed = feedRepository.unsubscribe(url)
if (feed == null) {
- val message = string(R.string.error_unsubscribe_feed)
+ val message = string(I18R.string.feat_main_error_unsubscribe_feed)
logger.log(message)
}
}
diff --git a/features/main/src/main/java/com/m3u/features/main/components/MainDialog.kt b/features/main/src/main/java/com/m3u/features/main/components/MainDialog.kt
index d6bd996b..10738d75 100644
--- a/features/main/src/main/java/com/m3u/features/main/components/MainDialog.kt
+++ b/features/main/src/main/java/com/m3u/features/main/components/MainDialog.kt
@@ -15,13 +15,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import com.m3u.data.database.entity.Feed
-import com.m3u.features.main.R
import com.m3u.ui.components.AppDialog
import com.m3u.ui.components.DialogItem
import com.m3u.ui.components.DialogTextField
import com.m3u.ui.ktx.animateDp
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
+import com.m3u.i18n.R as I18R
internal typealias OnUpdateStatus = (MainDialog) -> Unit
internal typealias OnUnsubscribe = (feedUrl: String) -> Unit
@@ -68,7 +68,7 @@ internal fun MainDialog(
var renamedText by remember(currentStatus) {
mutableStateOf(
with(currentStatus.feed) {
- if (editable) title else context.getString(R.string.imported_feed_title)
+ if (editable) title else context.getString(I18R.string.feat_main_imported_feed_title)
}
)
}
@@ -87,13 +87,13 @@ internal fun MainDialog(
}
)
if (!editMode) {
- DialogItem(R.string.unsubscribe_feed) {
+ DialogItem(I18R.string.feat_main_unsubscribe_feed) {
unsubscribe(currentStatus.feed.url)
update(MainDialog.Idle)
}
if (!currentStatus.feed.local) {
val clipboardManager = LocalClipboardManager.current
- DialogItem(R.string.copy_feed_url) {
+ DialogItem(I18R.string.feat_main_copy_feed_url) {
val annotatedString = AnnotatedString(currentStatus.feed.url)
clipboardManager.setText(annotatedString)
update(MainDialog.Idle)
diff --git a/features/main/src/main/res/values-zh-rCN/strings.xml b/features/main/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 8c7f24e6..00000000
--- a/features/main/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- 屏蔽的频道
- 取消订阅
- 取消订阅失败
- 复制链接
- 重命名
- 导入频道
-
\ No newline at end of file
diff --git a/features/main/src/main/res/values/strings.xml b/features/main/src/main/res/values/strings.xml
deleted file mode 100644
index f78cc0cc..00000000
--- a/features/main/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- banned lives
- unsubscribe
- cannot unsubscribe
- copy url
- rename
- imported
-
\ No newline at end of file
diff --git a/features/setting/src/main/java/com/m3u/features/setting/SettingViewModel.kt b/features/setting/src/main/java/com/m3u/features/setting/SettingViewModel.kt
index f61241ef..8b5f42db 100644
--- a/features/setting/src/main/java/com/m3u/features/setting/SettingViewModel.kt
+++ b/features/setting/src/main/java/com/m3u/features/setting/SettingViewModel.kt
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
+import com.m3u.i18n.R as I18R
@HiltViewModel
class SettingViewModel @Inject constructor(
@@ -159,7 +160,7 @@ class SettingViewModel @Inject constructor(
private fun subscribe() {
val title = writable.value.title
if (title.isEmpty()) {
- val message = string(R.string.error_empty_title)
+ val message = string(I18R.string.feat_setting_error_empty_title)
logger.log(message)
return
}
@@ -167,8 +168,8 @@ class SettingViewModel @Inject constructor(
val url = readable.actualUrl
if (url == null) {
val message = when {
- readable.localStorage -> string(R.string.error_unselected_file)
- else -> string(R.string.error_blank_url)
+ readable.localStorage -> string(I18R.string.feat_setting_error_unselected_file)
+ else -> string(I18R.string.feat_setting_error_blank_url)
}
logger.log(message)
return
@@ -186,7 +187,7 @@ class SettingViewModel @Inject constructor(
.addTag(url)
.build()
workManager.enqueue(request)
- val message = string(R.string.enqueue_subscribe)
+ val message = string(I18R.string.feat_setting_enqueue_subscribe)
logger.log(message)
writable.update {
it.copy(
diff --git a/features/setting/src/main/java/com/m3u/features/setting/fragments/PreferencesFragment.kt b/features/setting/src/main/java/com/m3u/features/setting/fragments/PreferencesFragment.kt
index ab8a46a4..bb3d9e4e 100644
--- a/features/setting/src/main/java/com/m3u/features/setting/fragments/PreferencesFragment.kt
+++ b/features/setting/src/main/java/com/m3u/features/setting/fragments/PreferencesFragment.kt
@@ -27,13 +27,13 @@ import com.m3u.core.annotation.OnClipMode
import com.m3u.core.annotation.OnFeedStrategy
import com.m3u.features.setting.NavigateToAbout
import com.m3u.features.setting.NavigateToConsole
-import com.m3u.features.setting.R
import com.m3u.features.setting.components.CheckBoxPreference
import com.m3u.features.setting.components.IconPreference
import com.m3u.features.setting.components.Preference
import com.m3u.features.setting.components.TextPreference
import com.m3u.ui.Destination
import com.m3u.ui.model.LocalSpacing
+import com.m3u.i18n.R as I18R
@Composable
internal fun PreferencesFragment(
@@ -84,43 +84,43 @@ internal fun PreferencesFragment(
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
Preference(
- title = stringResource(R.string.feed_management),
+ title = stringResource(I18R.string.feat_setting_feed_management),
enabled = true,
onClick = onFeedManagement
)
TextPreference(
- title = stringResource(R.string.sync_mode),
+ title = stringResource(I18R.string.feat_setting_sync_mode),
content = when (feedStrategy) {
- FeedStrategy.ALL -> stringResource(R.string.sync_mode_all)
- FeedStrategy.SKIP_FAVORITE -> stringResource(R.string.sync_mode_skip_favourite)
+ FeedStrategy.ALL -> stringResource(I18R.string.feat_setting_sync_mode_all)
+ FeedStrategy.SKIP_FAVORITE -> stringResource(I18R.string.feat_setting_sync_mode_skip_favourite)
else -> ""
},
onClick = onFeedStrategy
)
TextPreference(
- title = stringResource(R.string.clip_mode),
+ title = stringResource(I18R.string.feat_setting_clip_mode),
content = when (clipMode) {
- ClipMode.ADAPTIVE -> stringResource(R.string.clip_mode_adaptive)
- ClipMode.CLIP -> stringResource(R.string.clip_mode_clip)
- ClipMode.STRETCHED -> stringResource(R.string.clip_mode_stretched)
+ ClipMode.ADAPTIVE -> stringResource(I18R.string.feat_setting_clip_mode_adaptive)
+ ClipMode.CLIP -> stringResource(I18R.string.feat_setting_clip_mode_clip)
+ ClipMode.STRETCHED -> stringResource(I18R.string.feat_setting_clip_mode_stretched)
else -> ""
},
onClick = onClipMode
)
TextPreference(
- title = stringResource(R.string.connect_timeout),
+ title = stringResource(I18R.string.feat_setting_connect_timeout),
content = "${connectTimeout / 1000}s",
onClick = onConnectTimeout
)
TextPreference(
- title = stringResource(R.string.initial_tab),
+ title = stringResource(I18R.string.feat_setting_initial_tab),
content = stringResource(Destination.Root.entries[initialRootDestination].iconTextId),
onClick = onInitialTabIndex
)
CheckBoxPreference(
- title = stringResource(R.string.auto_refresh),
- subtitle = stringResource(R.string.auto_refresh_description),
+ title = stringResource(I18R.string.feat_setting_auto_refresh),
+ subtitle = stringResource(I18R.string.feat_setting_auto_refresh_description),
checked = autoRefresh,
onCheckedChange = { newValue ->
if (newValue != autoRefresh) {
@@ -129,8 +129,8 @@ internal fun PreferencesFragment(
}
)
CheckBoxPreference(
- title = stringResource(R.string.no_picture_mode),
- subtitle = stringResource(R.string.no_picture_mode_description),
+ title = stringResource(I18R.string.feat_setting_no_picture_mode),
+ subtitle = stringResource(I18R.string.feat_setting_no_picture_mode_description),
checked = noPictureMode,
onCheckedChange = { newValue ->
if (newValue != noPictureMode) {
@@ -139,8 +139,8 @@ internal fun PreferencesFragment(
}
)
CheckBoxPreference(
- title = stringResource(R.string.full_info_player),
- subtitle = stringResource(R.string.full_info_player_description),
+ title = stringResource(I18R.string.feat_setting_full_info_player),
+ subtitle = stringResource(I18R.string.feat_setting_full_info_player_description),
checked = fullInfoPlayer,
onCheckedChange = { newValue ->
if (newValue != fullInfoPlayer) {
@@ -149,8 +149,8 @@ internal fun PreferencesFragment(
}
)
CheckBoxPreference(
- title = stringResource(R.string.god_mode),
- subtitle = stringResource(R.string.god_mode_description),
+ title = stringResource(I18R.string.feat_setting_god_mode),
+ subtitle = stringResource(I18R.string.feat_setting_god_mode_description),
checked = godMode,
onCheckedChange = { newValue ->
if (newValue != godMode) {
@@ -159,9 +159,9 @@ internal fun PreferencesFragment(
}
)
CheckBoxPreference(
- title = stringResource(R.string.common_ui_mode),
- subtitle = if (useCommonUIModeEnable) stringResource(R.string.common_ui_mode_description)
- else stringResource(R.string.common_ui_mode_disabled_description),
+ title = stringResource(I18R.string.feat_setting_common_ui_mode),
+ subtitle = if (useCommonUIModeEnable) stringResource(I18R.string.feat_setting_common_ui_mode_description)
+ else stringResource(I18R.string.feat_setting_common_ui_mode_disabled_description),
enabled = useCommonUIModeEnable,
checked = useCommonUIMode,
onCheckedChange = { newValue ->
@@ -181,8 +181,8 @@ internal fun PreferencesFragment(
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
CheckBoxPreference(
- title = stringResource(R.string.experimental_mode),
- subtitle = stringResource(R.string.experimental_mode_description),
+ title = stringResource(I18R.string.feat_setting_experimental_mode),
+ subtitle = stringResource(I18R.string.feat_setting_experimental_mode_description),
checked = experimentalMode,
onCheckedChange = { newValue ->
if (newValue != experimentalMode) {
@@ -203,8 +203,8 @@ internal fun PreferencesFragment(
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
CheckBoxPreference(
- title = stringResource(R.string.cinema_mode),
- subtitle = stringResource(R.string.cinema_mode_description),
+ title = stringResource(I18R.string.feat_setting_cinema_mode),
+ subtitle = stringResource(I18R.string.feat_setting_cinema_mode_description),
checked = cinemaMode,
onCheckedChange = { newValue ->
if (newValue != cinemaMode) {
@@ -213,16 +213,16 @@ internal fun PreferencesFragment(
}
)
Preference(
- title = stringResource(R.string.script_management),
+ title = stringResource(I18R.string.feat_setting_script_management),
enabled = true,
onClick = onScriptManagement
)
Preference(
- title = stringResource(R.string.console_editor),
+ title = stringResource(I18R.string.feat_setting_console_editor),
onClick = navigateToConsole
)
CheckBoxPreference(
- title = stringResource(R.string.scroll_mode),
+ title = stringResource(I18R.string.feat_setting_scroll_mode),
checked = scrollMode,
onCheckedChange = { newValue ->
if (newValue != scrollMode) {
@@ -231,8 +231,8 @@ internal fun PreferencesFragment(
}
)
CheckBoxPreference(
- title = stringResource(R.string.ssl_verification_enabled),
- subtitle = stringResource(R.string.ssl_verification_enabled_description),
+ title = stringResource(I18R.string.feat_setting_ssl_verification_enabled),
+ subtitle = stringResource(I18R.string.feat_setting_ssl_verification_enabled_description),
checked = isSSLVerificationEnabled,
onCheckedChange = { newValue ->
if (newValue != isSSLVerificationEnabled) {
@@ -253,7 +253,7 @@ internal fun PreferencesFragment(
) {
val context = LocalContext.current
IconPreference(
- title = stringResource(R.string.system_setting),
+ title = stringResource(I18R.string.feat_setting_system_setting),
imageVector = Icons.AutoMirrored.Rounded.OpenInNew,
onClick = {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
@@ -264,17 +264,17 @@ internal fun PreferencesFragment(
)
// TODO: https://www.dropbox.com/developers/documentation/http/documentation#file_requests-list
Preference(
- title = stringResource(R.string.dropbox).uppercase(),
+ title = stringResource(I18R.string.feat_setting_dropbox).uppercase(),
onClick = navigateToAbout,
enabled = false
)
Preference(
- title = stringResource(R.string.project_about),
+ title = stringResource(I18R.string.feat_setting_project_about),
onClick = navigateToAbout,
// enabled = false
)
Preference(
- title = stringResource(R.string.app_version),
+ title = stringResource(I18R.string.feat_setting_app_version),
subtitle = version
)
}
diff --git a/features/setting/src/main/java/com/m3u/features/setting/fragments/ScriptsFragment.kt b/features/setting/src/main/java/com/m3u/features/setting/fragments/ScriptsFragment.kt
index 74212d7f..084f96f7 100644
--- a/features/setting/src/main/java/com/m3u/features/setting/fragments/ScriptsFragment.kt
+++ b/features/setting/src/main/java/com/m3u/features/setting/fragments/ScriptsFragment.kt
@@ -7,9 +7,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.m3u.features.setting.R
import com.m3u.ui.components.Button
import com.m3u.ui.components.OuterColumn
+import com.m3u.i18n.R as I18R
@Composable
internal fun ScriptsFragment(
@@ -33,7 +33,7 @@ internal fun ScriptsFragment(
}
Button(
- textRes = R.string.script_management_import_js,
+ textRes = I18R.string.feat_setting_script_management_import_js,
modifier = Modifier.fillMaxWidth(),
onClick = {
launcher.launch(arrayOf("text/javascript"))
diff --git a/features/setting/src/main/java/com/m3u/features/setting/fragments/SubscriptionsFragment.kt b/features/setting/src/main/java/com/m3u/features/setting/fragments/SubscriptionsFragment.kt
index b85ce9e5..8640358b 100644
--- a/features/setting/src/main/java/com/m3u/features/setting/fragments/SubscriptionsFragment.kt
+++ b/features/setting/src/main/java/com/m3u/features/setting/fragments/SubscriptionsFragment.kt
@@ -39,13 +39,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.m3u.core.util.readContentFilename
import com.m3u.data.database.entity.Live
-import com.m3u.features.setting.R
import com.m3u.features.setting.components.MutedLiveItem
import com.m3u.ui.components.Button
import com.m3u.ui.components.LabelField
import com.m3u.ui.components.TextButton
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
+import com.m3u.i18n.R as I18R
@Composable
internal fun SubscriptionsFragment(
@@ -80,7 +80,7 @@ internal fun SubscriptionsFragment(
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
Text(
- text = stringResource(R.string.label_muted_lives),
+ text = stringResource(I18R.string.feat_setting_label_muted_lives),
style = MaterialTheme.typography.button,
color = theme.onTint,
modifier = Modifier
@@ -105,7 +105,7 @@ internal fun SubscriptionsFragment(
item {
LabelField(
text = title,
- placeholder = stringResource(R.string.placeholder_title).uppercase(),
+ placeholder = stringResource(I18R.string.feat_setting_placeholder_title).uppercase(),
onValueChange = onTitle,
keyboardActions = KeyboardActions(
onNext = {
@@ -124,7 +124,7 @@ internal fun SubscriptionsFragment(
if (!localStorage) {
LabelField(
text = url,
- placeholder = stringResource(R.string.placeholder_url).uppercase(),
+ placeholder = stringResource(I18R.string.feat_setting_placeholder_url).uppercase(),
onValueChange = onUrl,
keyboardActions = KeyboardActions(
onDone = {
@@ -154,7 +154,7 @@ internal fun SubscriptionsFragment(
item {
Column {
Button(
- text = stringResource(R.string.label_subscribe),
+ text = stringResource(I18R.string.feat_setting_label_subscribe),
onClick = onSubscribe,
modifier = Modifier.fillMaxWidth()
)
@@ -189,7 +189,7 @@ fun LocalStorageSwitch(
horizontalArrangement = Arrangement.spacedBy(spacing.medium)
) {
Text(
- text = stringResource(R.string.local_storage),
+ text = stringResource(I18R.string.feat_setting_local_storage),
style = MaterialTheme.typography.subtitle1,
modifier = Modifier.weight(1f)
)
@@ -214,7 +214,7 @@ private fun LocalStorageButton(
val icon = Icons.AutoMirrored.Rounded.OpenInNew
val text = if (selected) remember(uri) {
uri?.readContentFilename(context.contentResolver).orEmpty()
- } else stringResource(R.string.label_select_from_local_storage)
+ } else stringResource(I18R.string.feat_setting_label_select_from_local_storage)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@@ -262,7 +262,7 @@ private fun ClipboardButton(
val clipboardManager = LocalClipboardManager.current
TextButton(
enabled = enabled,
- text = stringResource(R.string.label_parse_from_clipboard),
+ text = stringResource(I18R.string.feat_setting_label_parse_from_clipboard),
onClick = {
val clipboardUrl = clipboardManager.getText()?.text.orEmpty()
val clipboardTitle = run {
diff --git a/features/setting/src/main/res/values-zh-rCN/strings.xml b/features/setting/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index f2bb0328..00000000
--- a/features/setting/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
- 订阅链接
- 应用版本
- 订阅
-
- 订阅成功
- URL错误(%s)
- 订阅名称为空
- 订阅名称
- 订阅中…
-
- 所有
- 绕过收藏夹
- 同步策略
-
- 默认布局
- 使用手机设备布局模式
- 手机设备不可调整
-
- 脚本管理
- 订阅管理
- 最大超时时间
- 上帝模式
- 通过物理按键调节界面
- console editor
- 试验特性
- 不稳定的功能可能引发致命错误
- 屏蔽的频道
- 新增订阅
- 解析剪贴板
- 选择文件
- 解析本地文件
- 视频裁剪模式
- 自适应
- 裁剪
- 拉伸
- 滑动切换频道
- 自动刷新
- 进入播放列表自动刷新
- SSL证书校验
- 校验网络连接安全性
- 详细信息的播放器
- 播放器中展示更多的信息
- 初始界面
- 无图模式
- 可能会对性能有所提升
- 系统设置
- 影院模式
- 使用OLED纯黑主题
- 导入Javascript
- 订阅任务已添加至队列
- dropbox
- 关于项目
- 本地导入
- 还未选择文件
- 目标地址为空
-
\ No newline at end of file
diff --git a/features/setting/src/main/res/values/strings.xml b/features/setting/src/main/res/values/strings.xml
deleted file mode 100644
index 6518ce36..00000000
--- a/features/setting/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
- playlist link
- app version
- subscribe
- subscribe successfully
- malformed url (%s)
- playlist name is empty
- playlist name
- subscribing
- all
- skip favourite
- synchronization strategy
- UI mode
- use mobile device UI mode
- mobile device could not adjust this
- script management
- playlist management
- connect timeout
- god mode
- adjust layouts by physical volume buttons
- Console Editor
- experimental mode
- unstable features can throw fatal errors
- banned lives
- add playlist
- parse clipboard
- select file
- parse file
- video clip mode
- adaptive
- clip
- stretched
- scroll to change lives
- auto refresh
- automatically refreshed when enter the playlist
- SSL certificate verification
- ensure valid and trustworthy SSL/TLS certificates for secure communications
- full information player
- display more information in the player
- initial Screen
- no picture mode
- may improve performance
- system setting
- cinema mode
- use OLED black theme
- import Javascript
- a subscription task has been added to the queue
- dropbox
- about project
- local storage
- no file selected yet
- blank url
-
\ No newline at end of file
diff --git a/i18n/.gitignore b/i18n/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/i18n/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
new file mode 100644
index 00000000..8f8d5207
--- /dev/null
+++ b/i18n/build.gradle.kts
@@ -0,0 +1,38 @@
+plugins {
+ alias(libs.plugins.com.android.library)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+}
+
+android {
+ namespace = "com.m3u.i18n"
+ compileSdk = 33
+
+ defaultConfig {
+ minSdk = 26
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.core.ktx)
+ implementation(libs.androidx.appcompat.appcompat)
+}
\ No newline at end of file
diff --git a/i18n/consumer-rules.pro b/i18n/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/i18n/proguard-rules.pro b/i18n/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/i18n/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/i18n/src/main/AndroidManifest.xml b/i18n/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..44008a43
--- /dev/null
+++ b/i18n/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/app.xml b/i18n/src/main/res/values-zh-rCN/app.xml
new file mode 100644
index 00000000..44ac4bae
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/app.xml
@@ -0,0 +1,4 @@
+
+
+ 应用发生崩溃
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/data.xml b/i18n/src/main/res/values-zh-rCN/data.xml
new file mode 100644
index 00000000..fb74d0dc
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/data.xml
@@ -0,0 +1,5 @@
+
+
+ 文件不存在
+ 订阅名称为空
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_about.xml b/i18n/src/main/res/values-zh-rCN/feat_about.xml
new file mode 100644
index 00000000..41687cb5
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_about.xml
@@ -0,0 +1,4 @@
+
+
+ 关于项目
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_console.xml b/i18n/src/main/res/values-zh-rCN/feat_console.xml
new file mode 100644
index 00000000..386c57d1
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_console.xml
@@ -0,0 +1,4 @@
+
+
+ Console Editor
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_favourite.xml b/i18n/src/main/res/values-zh-rCN/feat_favourite.xml
new file mode 100644
index 00000000..312054b8
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_favourite.xml
@@ -0,0 +1,4 @@
+
+
+ 未知
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_feed.xml b/i18n/src/main/res/values-zh-rCN/feat_feed.xml
new file mode 100644
index 00000000..9d1080f3
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_feed.xml
@@ -0,0 +1,11 @@
+
+
+ 未知
+ 屏蔽这个频道?
+ 喜欢
+ 取消喜欢
+ 屏蔽
+ 订阅不存在(%s)
+ 保存封面
+ 输入关键字
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_live.xml b/i18n/src/main/res/values-zh-rCN/feat_live.xml
new file mode 100644
index 00000000..893f11d3
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_live.xml
@@ -0,0 +1,17 @@
+
+
+ 空闲
+ 缓冲中
+ 就绪
+ 结束
+ 返回
+ 静音
+ 取消静音
+ 收藏
+ 取消收藏
+ 录制
+ 停止录制
+ 投屏
+ 画中画模式
+ DLNA 设备
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_main.xml b/i18n/src/main/res/values-zh-rCN/feat_main.xml
new file mode 100644
index 00000000..1c983ff6
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_main.xml
@@ -0,0 +1,9 @@
+
+
+ 屏蔽的频道
+ 取消订阅
+ 取消订阅失败
+ 复制链接
+ 重命名
+ 导入频道
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/feat_setting.xml b/i18n/src/main/res/values-zh-rCN/feat_setting.xml
new file mode 100644
index 00000000..2b70fdfb
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/feat_setting.xml
@@ -0,0 +1,58 @@
+
+
+ 订阅链接
+ 应用版本
+ 订阅
+
+ 订阅成功
+ URL错误(%s)
+ 订阅名称为空
+ 订阅名称
+ 订阅中…
+
+ 所有
+ 绕过收藏夹
+ 同步策略
+
+ 默认布局
+ 使用手机设备布局模式
+ 手机设备不可调整
+
+ 脚本管理
+ 订阅管理
+ 最大超时时间
+ 上帝模式
+ 通过物理按键调节界面
+ console editor
+ 试验特性
+ 不稳定的功能可能引发致命错误
+ 屏蔽的频道
+ 新增订阅
+ 解析剪贴板
+ 选择文件
+ 解析本地文件
+ 视频裁剪模式
+ 自适应
+ 裁剪
+ 拉伸
+ 滑动切换频道
+ 自动刷新
+ 进入播放列表自动刷新
+ SSL证书校验
+ 校验网络连接安全性
+ 详细信息的播放器
+ 播放器中展示更多的信息
+ 初始界面
+ 无图模式
+ 可能会对性能有所提升
+ 系统设置
+ 影院模式
+ 使用OLED纯黑主题
+ 导入Javascript
+ 订阅任务已添加至队列
+ dropbox
+ 关于项目
+ 本地导入
+ 还未选择文件
+ 目标地址为空
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values-zh-rCN/ui.xml b/i18n/src/main/res/values-zh-rCN/ui.xml
new file mode 100644
index 00000000..01b83d08
--- /dev/null
+++ b/i18n/src/main/res/values-zh-rCN/ui.xml
@@ -0,0 +1,17 @@
+
+
+ M3U
+
+ 主页
+ 喜欢
+ 设置
+
+ 设置
+ 喜欢
+
+ 未知错误
+ 返回
+
+ 您
+ 好
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/app.xml b/i18n/src/main/res/values/app.xml
new file mode 100644
index 00000000..51358ced
--- /dev/null
+++ b/i18n/src/main/res/values/app.xml
@@ -0,0 +1,4 @@
+
+
+ app crashes
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/data.xml b/i18n/src/main/res/values/data.xml
new file mode 100644
index 00000000..6c2c3135
--- /dev/null
+++ b/i18n/src/main/res/values/data.xml
@@ -0,0 +1,5 @@
+
+
+ file not found
+ playlist name is empty
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_about.xml b/i18n/src/main/res/values/feat_about.xml
new file mode 100644
index 00000000..4d3eac13
--- /dev/null
+++ b/i18n/src/main/res/values/feat_about.xml
@@ -0,0 +1,4 @@
+
+
+ about project
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_console.xml b/i18n/src/main/res/values/feat_console.xml
new file mode 100644
index 00000000..386c57d1
--- /dev/null
+++ b/i18n/src/main/res/values/feat_console.xml
@@ -0,0 +1,4 @@
+
+
+ Console Editor
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_favourite.xml b/i18n/src/main/res/values/feat_favourite.xml
new file mode 100644
index 00000000..3913c202
--- /dev/null
+++ b/i18n/src/main/res/values/feat_favourite.xml
@@ -0,0 +1,4 @@
+
+
+ unknown
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_feed.xml b/i18n/src/main/res/values/feat_feed.xml
new file mode 100644
index 00000000..5407858f
--- /dev/null
+++ b/i18n/src/main/res/values/feat_feed.xml
@@ -0,0 +1,11 @@
+
+
+ unknown
+ do you want to ban this live?
+ like
+ cancel like
+ ban
+ playlist is not existed (%s)
+ save to gallery
+ enter key word
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_live.xml b/i18n/src/main/res/values/feat_live.xml
new file mode 100644
index 00000000..c8a798b4
--- /dev/null
+++ b/i18n/src/main/res/values/feat_live.xml
@@ -0,0 +1,18 @@
+
+
+ idle
+ buffering
+ ready
+ completed
+
+ back
+ mute
+ unmute
+ favourite
+ unfavourite
+ record
+ unrecord
+ cast screen
+ PIP mode
+ DLNA Devices
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_main.xml b/i18n/src/main/res/values/feat_main.xml
new file mode 100644
index 00000000..5088d17a
--- /dev/null
+++ b/i18n/src/main/res/values/feat_main.xml
@@ -0,0 +1,9 @@
+
+
+ banned lives
+ unsubscribe
+ cannot unsubscribe
+ copy url
+ rename
+ imported
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/feat_setting.xml b/i18n/src/main/res/values/feat_setting.xml
new file mode 100644
index 00000000..20792103
--- /dev/null
+++ b/i18n/src/main/res/values/feat_setting.xml
@@ -0,0 +1,54 @@
+
+
+ playlist link
+ app version
+ subscribe
+ subscribe successfully
+ malformed url (%s)
+ playlist name is empty
+ playlist name
+ subscribing
+ all
+ skip favourite
+ synchronization strategy
+ UI mode
+ use mobile device UI mode
+ mobile device could not adjust this
+ script management
+ playlist management
+ connect timeout
+ god mode
+ adjust layouts by physical volume buttons
+ Console Editor
+ experimental mode
+ unstable features can throw fatal errors
+ banned lives
+ add playlist
+ parse clipboard
+ select file
+ parse file
+ video clip mode
+ adaptive
+ clip
+ stretched
+ scroll to change lives
+ auto refresh
+ automatically refreshed when enter the playlist
+ SSL certificate verification
+ ensure valid and trustworthy SSL/TLS certificates for secure communications
+ full information player
+ display more information in the player
+ initial Screen
+ no picture mode
+ may improve performance
+ system setting
+ cinema mode
+ use OLED black theme
+ import Javascript
+ a subscription task has been added to the queue
+ dropbox
+ about project
+ local storage
+ no file selected yet
+ blank url
+
\ No newline at end of file
diff --git a/i18n/src/main/res/values/ui.xml b/i18n/src/main/res/values/ui.xml
new file mode 100644
index 00000000..786f7503
--- /dev/null
+++ b/i18n/src/main/res/values/ui.xml
@@ -0,0 +1,17 @@
+
+
+ M3U
+
+ For You
+ Favourite
+ Settings
+
+ Settings
+ Favourite
+
+ unknown error
+ back
+
+ ho
+ la
+
\ No newline at end of file
diff --git a/lint/build.gradle.kts b/lint/build.gradle.kts
index 43980ad3..d6cdbc7c 100644
--- a/lint/build.gradle.kts
+++ b/lint/build.gradle.kts
@@ -5,5 +5,5 @@ plugins {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
- compileOnly("com.android.tools.lint:lint:31.1.2")
+ compileOnly(libs.com.android.tools.lint.lint.api)
}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 57ddafb2..1fde1f2c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -32,3 +32,4 @@ include(
)
include(":benchmark")
include(":lint")
+include(":i18n")
diff --git a/ui/src/main/java/com/m3u/ui/Destination.kt b/ui/src/main/java/com/m3u/ui/Destination.kt
index 1001a8a0..f57a3fe9 100644
--- a/ui/src/main/java/com/m3u/ui/Destination.kt
+++ b/ui/src/main/java/com/m3u/ui/Destination.kt
@@ -9,6 +9,7 @@ import androidx.compose.material.icons.rounded.Collections
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.ui.graphics.vector.ImageVector
+import com.m3u.i18n.R as I18R
typealias Navigate = (Destination) -> Unit
@@ -22,20 +23,20 @@ sealed interface Destination {
Main(
selectedIcon = Icons.Rounded.Home,
unselectedIcon = Icons.Outlined.Home,
- iconTextId = R.string.destination_main,
- titleTextId = R.string.app_name
+ iconTextId = I18R.string.ui_destination_main,
+ titleTextId = I18R.string.ui_app_name
),
Favourite(
selectedIcon = Icons.Rounded.Collections,
unselectedIcon = Icons.Outlined.Collections,
- iconTextId = R.string.destination_favourite,
- titleTextId = R.string.title_favourite
+ iconTextId = I18R.string.ui_destination_favourite,
+ titleTextId = I18R.string.ui_title_favourite
),
Setting(
selectedIcon = Icons.Rounded.Settings,
unselectedIcon = Icons.Outlined.Settings,
- iconTextId = R.string.destination_setting,
- titleTextId = R.string.title_setting
+ iconTextId = I18R.string.ui_destination_setting,
+ titleTextId = I18R.string.ui_title_setting
);
companion object : Key
diff --git a/ui/src/main/java/com/m3u/ui/components/AppBar.kt b/ui/src/main/java/com/m3u/ui/components/AppBar.kt
index b43dce63..b3bebf84 100644
--- a/ui/src/main/java/com/m3u/ui/components/AppBar.kt
+++ b/ui/src/main/java/com/m3u/ui/components/AppBar.kt
@@ -57,7 +57,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.lerp
-import com.m3u.ui.R
+import com.m3u.i18n.R as I18R
import com.m3u.ui.ktx.animated
import com.m3u.ui.model.LocalDuration
import com.m3u.ui.model.LocalSpacing
@@ -183,7 +183,7 @@ fun AppTopBar(
) {
IconButton(
icon = Icons.AutoMirrored.Rounded.ArrowBack,
- contentDescription = stringResource(R.string.cd_top_bar_on_back_pressed),
+ contentDescription = stringResource(I18R.string.ui_cd_top_bar_on_back_pressed),
onClick = { if (progress > 0) onBackPressed?.invoke() },
modifier = Modifier.wrapContentSize()
)
diff --git a/ui/src/main/java/com/m3u/ui/components/ThemeSelection.kt b/ui/src/main/java/com/m3u/ui/components/ThemeSelection.kt
index ac6af943..688c54dd 100644
--- a/ui/src/main/java/com/m3u/ui/components/ThemeSelection.kt
+++ b/ui/src/main/java/com/m3u/ui/components/ThemeSelection.kt
@@ -41,7 +41,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.m3u.ui.R
+import com.m3u.i18n.R as I18R
import com.m3u.ui.model.LocalSpacing
import com.m3u.ui.model.LocalTheme
import com.m3u.ui.model.SugarColors
@@ -236,8 +236,8 @@ private fun ColorPiece(
modifier = Modifier.fillMaxSize()
) {
Text(
- text = if (left) stringResource(R.string.theme_card_left)
- else stringResource(R.string.theme_card_right),
+ text = if (left) stringResource(I18R.string.ui_theme_card_left)
+ else stringResource(I18R.string.ui_theme_card_right),
style = MaterialTheme.typography.bodyLarge
.copy(
fontSize = if (left) 16.sp
diff --git a/ui/src/main/res/values-zh-rCN/strings.xml b/ui/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 66e003e6..00000000
--- a/ui/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- M3U
-
- 主页
- 喜欢
- 设置
-
- 设置
- 喜欢
-
- 未知错误
- 返回
-
- 您
- 好
-
\ No newline at end of file
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
deleted file mode 100644
index dc04ca9d..00000000
--- a/ui/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- M3U
-
- For You
- Favourite
- Settings
-
- Settings
- Favourite
-
- unknown error
- back
-
- ho
- la
-
\ No newline at end of file