feat(i18n): standardize English and Simplified Chinese localization across UI

feat(i18n): standardize English and Simplified Chinese UI strings

- Move hardcoded user-facing text from Compose screens and dialogs into string resources
- Add and refine English and zh-rCN translations for settings, theme, about, backup, TOC, and book info flows
- Keep internal logs, exceptions, parser text, and comments unchanged unless user-facing
- Preserve existing behavior while improving localization consistency
This commit is contained in:
dat-bi authored and Kudomaga committed 2026-05-22 01:56:08 +08:00
1 parent 43b401c52b
commit 7107b35728
34 files changed
+777 -381

No files matched your search

+1
View File
@@ -17,3 +17,4 @@ package-lock.json
# Kotlin 2.0 # Kotlin 2.0
.kotlin/ .kotlin/
/.vscode/settings.json /.vscode/settings.json
/.codex/skills
@@ -85,6 +85,9 @@ private fun MaterialAboutScreen(
versionName: String, versionName: String,
) { ) {
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
val privacyPolicyTitle = stringResource(R.string.about_privacy_policy_title)
val licenseTitle = stringResource(R.string.about_license_title)
val disclaimerTitle = stringResource(R.string.about_disclaimer_title)
AppScaffold( AppScaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -178,7 +181,7 @@ private fun MaterialAboutScreen(
onClick = { onClick = {
onIntent( onIntent(
AboutIntent.ShowMdFile( AboutIntent.ShowMdFile(
"隐私政策", privacyPolicyTitle,
"privacyPolicy.md" "privacyPolicy.md"
) )
) )
@@ -188,13 +191,13 @@ private fun MaterialAboutScreen(
SettingItemWithDivider { SettingItemWithDivider {
SettingItem( SettingItem(
title = stringResource(R.string.license), title = stringResource(R.string.license),
onClick = { onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md")) } onClick = { onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md")) }
) )
} }
SettingItemWithDivider { SettingItemWithDivider {
SettingItem( SettingItem(
title = stringResource(R.string.disclaimer), title = stringResource(R.string.disclaimer),
onClick = { onIntent(AboutIntent.ShowMdFile("免责声明", "disclaimer.md")) } onClick = { onIntent(AboutIntent.ShowMdFile(disclaimerTitle, "disclaimer.md")) }
) )
} }
SettingItemWithDivider { SettingItemWithDivider {
@@ -80,7 +80,7 @@ fun UpdateSheet(
) { ) {
val title = when (mode) { val title = when (mode) {
UpdateMode.UPDATE -> stringResource(R.string.check_update) UpdateMode.UPDATE -> stringResource(R.string.check_update)
UpdateMode.VIEW_LOG -> "已经更新至" UpdateMode.VIEW_LOG -> stringResource(R.string.about_installed_version_title)
} }
AppModalBottomSheet( AppModalBottomSheet(
@@ -99,7 +99,7 @@ fun UpdateSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
AppText( AppText(
text = "当前版本", text = stringResource(R.string.about_current_version),
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
@@ -114,7 +114,7 @@ fun UpdateSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
AppText( AppText(
text = "新版本", text = stringResource(R.string.about_new_version),
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
@@ -144,7 +144,7 @@ fun UpdateSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
AppText( AppText(
text = "渠道", text = stringResource(R.string.about_update_channel),
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.bodyMedium,
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
@@ -182,7 +182,7 @@ fun UpdateSheet(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
PrimaryButton( PrimaryButton(
onClick = onStartDownload, onClick = onStartDownload,
text = "更新", text = stringResource(R.string.about_update_action),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
@@ -152,17 +152,17 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
private fun saveLog() { private fun saveLog() {
execute { execute {
val backupPath = AppConfig.backupPath ?: run { val backupPath = AppConfig.backupPath ?: run {
_effects.tryEmit(AboutEffect.ShowToast("未设置备份目录")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_backup_dir_not_set)))
return@execute return@execute
} }
if (!AppConfig.recordLog) { if (!AppConfig.recordLog) {
_effects.tryEmit(AboutEffect.ShowToast("未开启日志记录,请去其他设置里打开记录日志")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_log_recording_disabled)))
delay(3000) delay(3000)
} }
val doc = FileDoc.fromUri(backupPath.toUri(), true) val doc = FileDoc.fromUri(backupPath.toUri(), true)
copyLogs(doc) copyLogs(doc)
copyHeapDump(doc) copyHeapDump(doc)
_effects.tryEmit(AboutEffect.ShowToast("已保存至备份目录")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_saved_to_backup_dir)))
}.onError { }.onError {
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true) AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
} }
@@ -171,21 +171,21 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
private fun createHeapDump() { private fun createHeapDump() {
execute { execute {
val backupPath = AppConfig.backupPath ?: run { val backupPath = AppConfig.backupPath ?: run {
_effects.tryEmit(AboutEffect.ShowToast("未设置备份目录")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_backup_dir_not_set)))
return@execute return@execute
} }
if (!AppConfig.recordHeapDump) { if (!AppConfig.recordHeapDump) {
_effects.tryEmit(AboutEffect.ShowToast("未开启堆转储记录,请去其他设置里打开记录堆转储")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_recording_disabled)))
delay(3000) delay(3000)
} }
_effects.tryEmit(AboutEffect.ShowToast("开始创建堆转储")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_creating)))
System.gc() System.gc()
CrashHandler.doHeapDump(true) CrashHandler.doHeapDump(true)
val doc = FileDoc.fromUri(backupPath.toUri(), true) val doc = FileDoc.fromUri(backupPath.toUri(), true)
if (!copyHeapDump(doc)) { if (!copyHeapDump(doc)) {
_effects.tryEmit(AboutEffect.ShowToast("未找到堆转储文件")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_not_found)))
} else { } else {
_effects.tryEmit(AboutEffect.ShowToast("已保存至备份目录")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_saved_to_backup_dir)))
} }
}.onError { }.onError {
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it) AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
@@ -197,7 +197,7 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
if (sheet is AboutSheet.Update) { if (sheet is AboutSheet.Update) {
val info = sheet.updateInfo val info = sheet.updateInfo
if (info.downloadUrl.isBlank() || info.fileName.isBlank()) { if (info.downloadUrl.isBlank() || info.fileName.isBlank()) {
_effects.tryEmit(AboutEffect.ShowToast("下载信息不完整")) _effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_download_info_incomplete)))
} else { } else {
_effects.tryEmit(AboutEffect.StartDownload(info.downloadUrl, info.fileName)) _effects.tryEmit(AboutEffect.StartDownload(info.downloadUrl, info.fileName))
} }
@@ -89,6 +89,7 @@ fun MiuixAboutScreen(
val topAppBarScrollBehavior = MiuixScrollBehavior() val topAppBarScrollBehavior = MiuixScrollBehavior()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val isWideScreen = shouldShowSplitPane() val isWideScreen = shouldShowSplitPane()
val licenseTitle = stringResource(R.string.about_license_title)
val scrollProgress by remember { val scrollProgress by remember {
derivedStateOf { derivedStateOf {
@@ -119,7 +120,7 @@ fun MiuixAboutScreen(
if (scrollProgress == 1f) MiuixTheme.colorScheme.surface else Color.Transparent if (scrollProgress == 1f) MiuixTheme.colorScheme.surface else Color.Transparent
} }
val navigator = remember { val navigator = remember(licenseTitle) {
object : MiuixNavigator { object : MiuixNavigator {
override fun pop() { override fun pop() {
onBack() onBack()
@@ -127,7 +128,7 @@ fun MiuixAboutScreen(
override fun push(route: Any) { override fun push(route: Any) {
if (route == "License") { if (route == "License") {
onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md")) onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md"))
} }
} }
} }
@@ -180,6 +181,9 @@ private fun AboutContent(
) { ) {
val appState = LocalAppState.current val appState = LocalAppState.current
val isWideScreen = LocalIsWideScreen.current val isWideScreen = LocalIsWideScreen.current
val privacyPolicyTitle = stringResource(R.string.about_privacy_policy_title)
val licenseTitle = stringResource(R.string.about_license_title)
val disclaimerTitle = stringResource(R.string.about_disclaimer_title)
val backdrop = rememberBlurBackdrop() val backdrop = rememberBlurBackdrop()
var blurRadius by remember { mutableFloatStateOf(60f) } var blurRadius by remember { mutableFloatStateOf(60f) }
@@ -427,7 +431,7 @@ private fun AboutContent(
onClick = { onClick = {
onIntent( onIntent(
AboutIntent.ShowMdFile( AboutIntent.ShowMdFile(
"隐私政策", privacyPolicyTitle,
"privacyPolicy.md" "privacyPolicy.md"
) )
) )
@@ -435,14 +439,14 @@ private fun AboutContent(
) )
ArrowPreference( ArrowPreference(
title = stringResource(R.string.license), title = stringResource(R.string.license),
onClick = { onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md")) }, onClick = { onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md")) },
) )
ArrowPreference( ArrowPreference(
title = stringResource(R.string.disclaimer), title = stringResource(R.string.disclaimer),
onClick = { onClick = {
onIntent( onIntent(
AboutIntent.ShowMdFile( AboutIntent.ShowMdFile(
"免责声明", disclaimerTitle,
"disclaimer.md" "disclaimer.md"
) )
) )
@@ -55,7 +55,7 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
val updateBody = arguments?.getString("updateBody") val updateBody = arguments?.getString("updateBody")
if (updateBody.isNullOrBlank()) { if (updateBody.isNullOrBlank()) {
toastOnUi("没有数据") toastOnUi(R.string.about_no_data)
dismiss() dismiss()
return return
} }
@@ -77,16 +77,16 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
binding.tvUrl.text = url binding.tvUrl.text = url
binding.btnUpdate.setOnClickListener { binding.btnUpdate.setOnClickListener {
if (url.isNullOrBlank() || fileName.isNullOrBlank()) { if (url.isNullOrBlank() || fileName.isNullOrBlank()) {
toastOnUi("下载信息不完整") toastOnUi(R.string.about_download_info_incomplete)
return@setOnClickListener return@setOnClickListener
} }
Download.start(requireContext(), url, fileName) Download.start(requireContext(), url, fileName)
toastOnUi("开始下载: $fileName") toastOnUi(getString(R.string.about_start_download, fileName))
} }
} }
Mode.VIEW_LOG -> { Mode.VIEW_LOG -> {
binding.bottomSheetTitle.text = "已经更新至" binding.bottomSheetTitle.setText(R.string.about_installed_version_title)
binding.tvVersion.text = BuildConfig.VERSION_NAME binding.tvVersion.text = BuildConfig.VERSION_NAME
binding.btnUpdate.gone() binding.btnUpdate.gone()
binding.llCurrent.gone() binding.llCurrent.gone()
@@ -178,7 +178,11 @@ fun BookInfoEditContent(
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
BookTypeDropdown( BookTypeDropdown(
bookTypes = uiState.bookTypes, bookTypes = listOf(
BookInfoEditType.TEXT to stringResource(R.string.book_type_text),
BookInfoEditType.AUDIO to stringResource(R.string.book_type_audio),
BookInfoEditType.IMAGE to stringResource(R.string.book_type_image)
),
selectedType = uiState.selectedType, selectedType = uiState.selectedType,
onTypeSelected = { viewModel.onBookTypeChange(it) } onTypeSelected = { viewModel.onBookTypeChange(it) }
) )
@@ -186,8 +190,8 @@ fun BookInfoEditContent(
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
SwitchSettingItem( SwitchSettingItem(
title = "固定书籍类型", title = stringResource(R.string.fixed_book_type),
description = "书籍更新后不覆盖书籍类型", description = stringResource(R.string.fixed_book_type_summary),
checked = uiState.fixedType, checked = uiState.fixedType,
onCheckedChange = { viewModel.onFixedTypeChange(it) } onCheckedChange = { viewModel.onFixedTypeChange(it) }
) )
@@ -201,7 +205,7 @@ fun BookInfoEditContent(
AppTextField( AppTextField(
value = uiState.name, value = uiState.name,
onValueChange = { viewModel.onNameChange(it) }, onValueChange = { viewModel.onNameChange(it) },
label = "书名", label = stringResource(R.string.book_name),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -209,7 +213,7 @@ fun BookInfoEditContent(
AppTextField( AppTextField(
value = uiState.author, value = uiState.author,
onValueChange = { viewModel.onAuthorChange(it) }, onValueChange = { viewModel.onAuthorChange(it) },
label = "作者", label = stringResource(R.string.author),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -217,7 +221,7 @@ fun BookInfoEditContent(
AppTextField( AppTextField(
value = uiState.coverUrl ?: "", value = uiState.coverUrl ?: "",
onValueChange = { viewModel.onCoverUrlChange(it) }, onValueChange = { viewModel.onCoverUrlChange(it) },
label = "封面链接", label = stringResource(R.string.cover_url),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -232,7 +236,7 @@ fun BookInfoEditContent(
AppTextField( AppTextField(
value = uiState.intro ?: "", value = uiState.intro ?: "",
onValueChange = { viewModel.onIntroChange(it) }, onValueChange = { viewModel.onIntroChange(it) },
label = "简介", label = stringResource(R.string.book_intro),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -240,7 +244,7 @@ fun BookInfoEditContent(
AppTextField( AppTextField(
value = uiState.remark ?: "", value = uiState.remark ?: "",
onValueChange = { viewModel.onRemarkChange(it) }, onValueChange = { viewModel.onRemarkChange(it) },
label = "备注", label = stringResource(R.string.book_remark),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -250,18 +254,19 @@ fun BookInfoEditContent(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookTypeDropdown( fun BookTypeDropdown(
bookTypes: List<String>, bookTypes: List<Pair<BookInfoEditType, String>>,
selectedType: String, selectedType: BookInfoEditType,
onTypeSelected: (String) -> Unit onTypeSelected: (BookInfoEditType) -> Unit
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val selectedTypeLabel = bookTypes.firstOrNull { it.first == selectedType }?.second.orEmpty()
val textFieldState = rememberTextFieldState( val textFieldState = rememberTextFieldState(
initialText = selectedType initialText = selectedTypeLabel
) )
LaunchedEffect(selectedType) { LaunchedEffect(selectedTypeLabel) {
textFieldState.setTextAndPlaceCursorAtEnd(selectedType) textFieldState.setTextAndPlaceCursorAtEnd(selectedTypeLabel)
} }
val bookInfoInputColor = ThemeConfig.bookInfoInputColor val bookInfoInputColor = ThemeConfig.bookInfoInputColor
@@ -280,7 +285,7 @@ fun BookTypeDropdown(
state = textFieldState, state = textFieldState,
readOnly = true, readOnly = true,
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
label = "书籍类型", label = stringResource(R.string.book_type_label),
backgroundColor = inputBackgroundColor, backgroundColor = inputBackgroundColor,
trailingIcon = { trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
@@ -297,11 +302,11 @@ fun BookTypeDropdown(
expanded = expanded, expanded = expanded,
onDismissRequest = { expanded = false } onDismissRequest = { expanded = false }
) { ) {
bookTypes.forEach { option -> bookTypes.forEach { (type, label) ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = option, text = label,
onClick = { onClick = {
onTypeSelected(option) onTypeSelected(type)
expanded = false expanded = false
} }
) )
@@ -387,12 +392,16 @@ fun KindEditor(
AppAlertDialog( AppAlertDialog(
show = true, show = true,
onDismissRequest = { editingIndex = null }, onDismissRequest = { editingIndex = null },
title = if (isAdding) "添加标签" else "编辑标签", title = if (isAdding) {
stringResource(R.string.add_tag)
} else {
stringResource(R.string.edit_tag)
},
content = { content = {
AppTextField( AppTextField(
value = editText, value = editText,
onValueChange = { editText = it }, onValueChange = { editText = it },
label = "标签", label = stringResource(R.string.tag),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -25,6 +25,12 @@ import kotlinx.coroutines.flow.asStateFlow
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
enum class BookInfoEditType {
TEXT,
AUDIO,
IMAGE
}
data class BookInfoEditUiState( data class BookInfoEditUiState(
val name: String = "", val name: String = "",
val author: String = "", val author: String = "",
@@ -33,8 +39,7 @@ data class BookInfoEditUiState(
val remark: String? = null, val remark: String? = null,
val kindList: List<String> = emptyList(), val kindList: List<String> = emptyList(),
val originalKindList: List<String> = emptyList(), val originalKindList: List<String> = emptyList(),
val selectedType: String = "文本", val selectedType: BookInfoEditType = BookInfoEditType.TEXT,
val bookTypes: List<String> = listOf("文本", "音频", "图片"),
val fixedType: Boolean = false, val fixedType: Boolean = false,
val book: Book? = null, val book: Book? = null,
) )
@@ -49,9 +54,9 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
book = appDb.bookDao.getBook(bookUrl) book = appDb.bookDao.getBook(bookUrl)
book?.let { book?.let {
val selectedTypeIndex = when { val selectedTypeIndex = when {
it.isImage -> 2 it.isImage -> BookInfoEditType.IMAGE
it.isAudio -> 1 it.isAudio -> BookInfoEditType.AUDIO
else -> 0 else -> BookInfoEditType.TEXT
} }
val kinds = val kinds =
it.kind?.split(",", "\n")?.filter { kind -> kind.isNotBlank() }.orEmpty() it.kind?.split(",", "\n")?.filter { kind -> kind.isNotBlank() }.orEmpty()
@@ -63,7 +68,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
remark = it.remark, remark = it.remark,
kindList = kinds, kindList = kinds,
originalKindList = kinds, originalKindList = kinds,
selectedType = _uiState.value.bookTypes[selectedTypeIndex], selectedType = selectedTypeIndex,
fixedType = it.config.fixedType, fixedType = it.config.fixedType,
book = it book = it
) )
@@ -99,7 +104,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
_uiState.value = _uiState.value.copy(kindList = kindList) _uiState.value = _uiState.value.copy(kindList = kindList)
} }
fun onBookTypeChange(bookType: String) { fun onBookTypeChange(bookType: BookInfoEditType) {
_uiState.value = _uiState.value.copy(selectedType = bookType) _uiState.value = _uiState.value.copy(selectedType = bookType)
} }
@@ -121,8 +126,8 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
book.remark = currentState.remark book.remark = currentState.remark
val local = if (book.isLocal) BookType.local else 0 val local = if (book.isLocal) BookType.local else 0
val bookType = when (currentState.selectedType) { val bookType = when (currentState.selectedType) {
currentState.bookTypes[2] -> BookType.image or local BookInfoEditType.IMAGE -> BookType.image or local
currentState.bookTypes[1] -> BookType.audio or local BookInfoEditType.AUDIO -> BookType.audio or local
else -> BookType.text or local else -> BookType.text or local
} }
book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text) book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text)
@@ -40,4 +40,4 @@ class TocActivity : BaseComposeActivity() {
} }
) )
} }
} }
@@ -71,12 +71,14 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.book.isLocal import io.legado.app.help.book.isLocal
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
@@ -151,6 +153,15 @@ fun TocScreen(
val useReplace = viewModel.useReplace val useReplace = viewModel.useReplace
val showWordCount = viewModel.showWordCount val showWordCount = viewModel.showWordCount
val bookmarkManagementTitle = stringResource(R.string.bookmark_management)
val locateCurrentReadingText = stringResource(R.string.locate_current_reading)
val moveToTopText = stringResource(R.string.move_to_top)
val moveToBottomText = stringResource(R.string.move_to_bottom)
val downloadAllText = stringResource(R.string.download_all)
val invertSelectionText = stringResource(R.string.invert_selection)
val selectFollowingText = stringResource(R.string.select_following)
val addBookmarkText = stringResource(R.string.bookmark_add)
val bookmarkDefaultFileName = stringResource(R.string.bookmark)
val topBarTitle = remember( val topBarTitle = remember(
pagerState.currentPage, pagerState.currentPage,
@@ -162,7 +173,7 @@ fun TocScreen(
book?.durChapterTitle?.takeIf { it.isNotBlank() } ?: (book?.name ?: "") book?.durChapterTitle?.takeIf { it.isNotBlank() } ?: (book?.name ?: "")
} }
1 -> "书签管理" 1 -> bookmarkManagementTitle
else -> book?.name ?: "" else -> book?.name ?: ""
} }
} }
@@ -206,9 +217,15 @@ fun TocScreen(
} }
} }
val fabItems = remember(state.items) { val fabItems = remember(
state.items,
locateCurrentReadingText,
moveToTopText,
moveToBottomText,
downloadAllText
) {
listOf( listOf(
FabMenuItem(Icons.Default.LocationOn, "定位至当前阅读") { FabMenuItem(Icons.Default.LocationOn, locateCurrentReadingText) {
scope.launch { scope.launch {
val target = state.items.indexOfFirst { it.isDur } val target = state.items.indexOfFirst { it.isDur }
if (target != -1) { if (target != -1) {
@@ -219,32 +236,37 @@ fun TocScreen(
} }
} }
}, },
FabMenuItem(Icons.Default.VerticalAlignTop, "移至顶部") { FabMenuItem(Icons.Default.VerticalAlignTop, moveToTopText) {
scope.launch { listState.animateScrollToItem(0) } scope.launch { listState.animateScrollToItem(0) }
}, },
FabMenuItem(Icons.Default.VerticalAlignBottom, "移至底部") { FabMenuItem(Icons.Default.VerticalAlignBottom, moveToBottomText) {
scope.launch { listState.animateScrollToItem(state.items.size) } scope.launch { listState.animateScrollToItem(state.items.size) }
}, },
FabMenuItem(Icons.Default.DownloadForOffline, "下载全部") { FabMenuItem(Icons.Default.DownloadForOffline, downloadAllText) {
viewModel.downloadAll() viewModel.downloadAll()
} }
) )
} }
val selectionSecondaryActions = remember(state.selectedIds) { val selectionSecondaryActions = remember(
state.selectedIds,
invertSelectionText,
selectFollowingText,
addBookmarkText
) {
listOf( listOf(
ActionItem( ActionItem(
text = "反选", text = invertSelectionText,
icon = Icons.Default.Refresh, icon = Icons.Default.Refresh,
onClick = { viewModel.invertSelection() } onClick = { viewModel.invertSelection() }
), ),
ActionItem( ActionItem(
text = "选择后续", text = selectFollowingText,
icon = Icons.Default.ExpandMore, icon = Icons.Default.ExpandMore,
onClick = { viewModel.selectFromLast() } onClick = { viewModel.selectFromLast() }
), ),
ActionItem( ActionItem(
text = "添加书签", text = addBookmarkText,
icon = Icons.Default.BookmarkAdd, icon = Icons.Default.BookmarkAdd,
onClick = { viewModel.addBookmarksForSelected() } onClick = { viewModel.addBookmarksForSelected() }
) )
@@ -332,35 +354,35 @@ fun TocScreen(
onBackClick = onBackClick, onBackClick = onBackClick,
onSearchToggle = { viewModel.setSearchMode(it) }, onSearchToggle = { viewModel.setSearchMode(it) },
onSearchQueryChange = { viewModel.setSearchKey(it) }, onSearchQueryChange = { viewModel.setSearchKey(it) },
searchPlaceholder = "搜索章节...", searchPlaceholder = stringResource(R.string.search_chapters),
onClearSelection = { viewModel.clearSelection() }, onClearSelection = { viewModel.clearSelection() },
dropDownMenuContent = { dismiss -> dropDownMenuContent = { dismiss ->
when (pagerState.currentPage) { when (pagerState.currentPage) {
0 -> { 0 -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "使用替换规则", text = stringResource(R.string.use_replace_rule),
isSelected = useReplace, isSelected = useReplace,
onClick = { viewModel.toggleUseReplace() } onClick = { viewModel.toggleUseReplace() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "显示字数", text = stringResource(R.string.show_word_count),
isSelected = showWordCount, isSelected = showWordCount,
onClick = { viewModel.toggleShowWordCount() } onClick = { viewModel.toggleShowWordCount() }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "反转目录", text = stringResource(R.string.reverse_toc),
onClick = { viewModel.reverseToc() } onClick = { viewModel.reverseToc() }
) )
PillDivider() PillDivider()
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "替换规则", text = stringResource(R.string.replace_rule_title),
onClick = { onClick = {
onOpenReplaceRule(null) onOpenReplaceRule(null)
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "新建替换规则", text = stringResource(R.string.add_replace_rule),
onClick = { onClick = {
val scopes = mutableListOf<String>() val scopes = mutableListOf<String>()
book?.name?.let { scopes.add(it) } book?.name?.let { scopes.add(it) }
@@ -378,9 +400,9 @@ fun TocScreen(
} }
) )
if (book?.isLocal == true) { if (book?.isLocal == true) {
PillHeaderDivider(title = "本地书籍选项") PillHeaderDivider(title = stringResource(R.string.local_book_options))
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "本地书籍目录规则", text = stringResource(R.string.local_book_toc_rule),
onClick = { onClick = {
val intent = val intent =
Intent(context, TxtTocRuleActivity::class.java).apply { Intent(context, TxtTocRuleActivity::class.java).apply {
@@ -391,7 +413,7 @@ fun TocScreen(
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "拆分超长章节", text = stringResource(R.string.split_long_chapters),
isSelected = viewModel.isSplitLongChapter, isSelected = viewModel.isSplitLongChapter,
onClick = { onClick = {
viewModel.toggleSplitLongChapter() viewModel.toggleSplitLongChapter()
@@ -403,25 +425,27 @@ fun TocScreen(
else -> { else -> {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "导出书签为JSON", text = stringResource(R.string.export_bookmarks_json),
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
Locale.getDefault() Locale.getDefault()
).format(Date()) ).format(Date())
val initialName = "${book?.name ?: "书签"}_$dateFormat.json" val initialName =
"${book?.name ?: bookmarkDefaultFileName}_$dateFormat.json"
exportLauncher.launch(initialName) exportLauncher.launch(initialName)
dismiss() dismiss()
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "导出书签为MarkDown", text = stringResource(R.string.export_bookmarks_markdown),
onClick = { onClick = {
val dateFormat = SimpleDateFormat( val dateFormat = SimpleDateFormat(
"yyyyMMdd_HHmm", "yyyyMMdd_HHmm",
Locale.getDefault() Locale.getDefault()
).format(Date()) ).format(Date())
val initialName = "${book?.name ?: "书签"}_$dateFormat.md" val initialName =
"${book?.name ?: bookmarkDefaultFileName}_$dateFormat.md"
exportLauncher.launch(initialName) exportLauncher.launch(initialName)
dismiss() dismiss()
} }
@@ -437,7 +461,10 @@ fun TocScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
AppTabRow( AppTabRow(
tabTitles = listOf("目录", "书签"), tabTitles = listOf(
stringResource(R.string.chapter_list),
stringResource(R.string.bookmark)
),
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
onTabSelected = { index -> onTabSelected = { index ->
scope.launch { scope.launch {
@@ -453,20 +480,20 @@ fun TocScreen(
checked = showVolumeMenu, checked = showVolumeMenu,
onCheckedChange = { showVolumeMenu = it }, onCheckedChange = { showVolumeMenu = it },
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted, imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
contentDescription = "卷管理" contentDescription = stringResource(R.string.volume_management)
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = showVolumeMenu, expanded = showVolumeMenu,
onDismissRequest = { showVolumeMenu = false } onDismissRequest = { showVolumeMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "展开所有卷", text = stringResource(R.string.expand_volume),
onClick = { onClick = {
viewModel.expandAllVolumes(); showVolumeMenu = false viewModel.expandAllVolumes(); showVolumeMenu = false
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "收起所有卷", text = stringResource(R.string.coll_volume),
onClick = { onClick = {
viewModel.collapseAllVolumes(); showVolumeMenu = false viewModel.collapseAllVolumes(); showVolumeMenu = false
} }
@@ -475,7 +502,7 @@ fun TocScreen(
val volumeItems = val volumeItems =
remember(state.items) { state.items.filter { it.isVolume } } remember(state.items) { state.items.filter { it.isVolume } }
if (volumeItems.isNotEmpty()) { if (volumeItems.isNotEmpty()) {
PillHeaderDivider(title = "快速跳转") PillHeaderDivider(title = stringResource(R.string.quick_jump))
volumeItems.forEach { uiItem -> volumeItems.forEach { uiItem ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = uiItem.title, text = uiItem.title,
@@ -528,7 +555,10 @@ fun TocScreen(
onSelectAll = { viewModel.selectAll() }, onSelectAll = { viewModel.selectAll() },
onSelectInvert = { viewModel.invertSelection() }, onSelectInvert = { viewModel.invertSelection() },
primaryAction = ActionItem( primaryAction = ActionItem(
text = "下载已选 (${state.selectedIds.size})", text = stringResource(
R.string.download_selected_count,
state.selectedIds.size
),
icon = Icons.Default.Download, icon = Icons.Default.Download,
onClick = { viewModel.downloadSelected() } onClick = { viewModel.downloadSelected() }
), ),
@@ -818,7 +848,7 @@ fun BookmarkListContent(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
EmptyMessage( EmptyMessage(
message = "暂无书签" message = stringResource(R.string.no_bookmark)
) )
} }
} else { } else {
@@ -7,6 +7,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.legado.app.R
import io.legado.app.base.BaseRuleViewModel import io.legado.app.base.BaseRuleViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
@@ -424,9 +425,13 @@ class TocViewModel(
val book = bookState.value ?: return val book = bookState.value ?: return
book.tocUrl = newRegex book.tocUrl = newRegex
upBookTocRule(book) { error -> upBookTocRule(book) { error ->
if (error != null) context.toastOnUi("更新目录规则失败: ${error.localizedMessage}") if (error != null) {
context.toastOnUi(
context.getString(R.string.toc_rule_update_failed, error.localizedMessage)
)
}
else { else {
context.toastOnUi("目录规则已更新") context.toastOnUi(R.string.toc_rule_updated)
if (ReadBook.book?.bookUrl == book.bookUrl) ReadBook.upMsg(null) if (ReadBook.book?.bookUrl == book.bookUrl) ReadBook.upMsg(null)
} }
} }
@@ -437,8 +442,14 @@ class TocViewModel(
val newState = !isSplitLongChapter val newState = !isSplitLongChapter
book.setSplitLongChapter(newState) book.setSplitLongChapter(newState)
upBookTocRule(book) { error -> upBookTocRule(book) { error ->
if (error != null) context.toastOnUi("设置失败: ${error.localizedMessage}") if (error != null) {
else context.toastOnUi(if (newState) "已开启长章节拆分" else "已关闭长章节拆分") context.toastOnUi(context.getString(R.string.setting_failed, error.localizedMessage))
} else {
context.toastOnUi(
if (newState) R.string.split_long_chapters_enabled
else R.string.split_long_chapters_disabled
)
}
} }
} }
@@ -467,16 +478,16 @@ class TocViewModel(
val book = bookState.value ?: return@launch val book = bookState.value ?: return@launch
val bookmarks = appDb.bookmarkDao.getByBook(book.name, book.author) val bookmarks = appDb.bookmarkDao.getByBook(book.name, book.author)
if (bookmarks.isEmpty()) { if (bookmarks.isEmpty()) {
context.toastOnUi("没有可导出的书签") context.toastOnUi(R.string.no_bookmarks_to_export)
return@launch return@launch
} }
BookmarkExporter.exportToUri( BookmarkExporter.exportToUri(
context = getApplication(), fileUri = fileUri, bookmarks = bookmarks, context = getApplication(), fileUri = fileUri, bookmarks = bookmarks,
isMd = isMd, bookName = book.name, author = book.author isMd = isMd, bookName = book.name, author = book.author
) )
context.toastOnUi("保存成功") context.toastOnUi(R.string.save_success)
} catch (e: Exception) { } catch (e: Exception) {
context.toastOnUi("保存失败: ${e.message}") context.toastOnUi(context.getString(R.string.save_failed_with_error, e.message))
} }
} }
@@ -495,7 +506,7 @@ class TocViewModel(
.toList() .toList()
if (selectedItems.isEmpty()) { if (selectedItems.isEmpty()) {
context.toastOnUi("请选择章节") context.toastOnUi(R.string.select_chapters)
return@launch return@launch
} }
@@ -512,7 +523,7 @@ class TocViewModel(
} }
appDb.bookmarkDao.insert(*bookmarks.toTypedArray()) appDb.bookmarkDao.insert(*bookmarks.toTypedArray())
context.toastOnUi("已添加 ${bookmarks.size} 个书签") context.toastOnUi(context.getString(R.string.bookmarks_added_count, bookmarks.size))
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
clearSelection() clearSelection()
} }
@@ -525,7 +536,9 @@ class TocViewModel(
execute { execute {
cacheBookChaptersUseCase.execute(book.bookUrl, indices) cacheBookChaptersUseCase.execute(book.bookUrl, indices)
}.onSuccess { count -> }.onSuccess { count ->
getApplication<Application>().toastOnUi("开始下载 $count 个章节") getApplication<Application>().toastOnUi(
context.getString(R.string.start_downloading_chapters, count)
)
clearSelection() clearSelection()
} }
} }
@@ -535,7 +548,7 @@ class TocViewModel(
execute { execute {
cacheBookChaptersUseCase.execute(book.bookUrl, listOf(index)) cacheBookChaptersUseCase.execute(book.bookUrl, listOf(index))
}.onSuccess { }.onSuccess {
getApplication<Application>().toastOnUi("开始下载章节") getApplication<Application>().toastOnUi(R.string.start_downloading_chapter)
} }
} }
@@ -546,14 +559,16 @@ class TocViewModel(
.map { it.id } .map { it.id }
if (targetIndices.isEmpty()) { if (targetIndices.isEmpty()) {
getApplication<Application>().toastOnUi("所有章节已缓存") getApplication<Application>().toastOnUi(R.string.all_chapters_cached)
return return
} }
execute { execute {
cacheBookChaptersUseCase.execute(book.bookUrl, targetIndices) cacheBookChaptersUseCase.execute(book.bookUrl, targetIndices)
}.onSuccess { count -> }.onSuccess { count ->
getApplication<Application>().toastOnUi("开始下载剩余 $count 个章节") getApplication<Application>().toastOnUi(
context.getString(R.string.start_downloading_remaining_chapters, count)
)
} }
} }
@@ -186,7 +186,7 @@ fun TxtRuleScreen(
) )
BatchImportDialog( BatchImportDialog(
title = "导入词典规则", title = stringResource(R.string.import_txt_toc_rule),
importState = importState, importState = importState,
onDismissRequest = { viewModel.cancelImport() }, onDismissRequest = { viewModel.cancelImport() },
onToggleItem = { viewModel.toggleImportSelection(it) }, onToggleItem = { viewModel.toggleImportSelection(it) },
@@ -262,7 +262,11 @@ fun TxtRuleScreen(
) )
RuleListScaffold( RuleListScaffold(
title = if (isPickMode) "选择目录规则" else "目录规则", title = if (isPickMode) {
stringResource(R.string.select_toc_rule)
} else {
stringResource(R.string.txt_toc_rule)
},
state = uiState, state = uiState,
onBackClick = { onBackClick() }, onBackClick = { onBackClick() },
onSearchToggle = { active -> onSearchToggle = { active ->
@@ -3,6 +3,7 @@ package io.legado.app.ui.book.toc.rule
import android.app.Application import android.app.Application
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.legado.app.R
import io.legado.app.base.BaseRuleViewModel import io.legado.app.base.BaseRuleViewModel
import io.legado.app.data.entities.TxtTocRule import io.legado.app.data.entities.TxtTocRule
import io.legado.app.data.repository.TxtTocRuleRepository import io.legado.app.data.repository.TxtTocRuleRepository
@@ -147,14 +148,14 @@ class TxtTocRuleViewModel(
fun pasteRule(): TxtTocRule? { fun pasteRule(): TxtTocRule? {
val text = context.getClipText() val text = context.getClipText()
if (text.isNullOrBlank()) { if (text.isNullOrBlank()) {
context.toastOnUi("剪贴板没有内容") context.toastOnUi(R.string.clipboard_empty)
return null return null
} }
return try { return try {
GSON.fromJsonObject<TxtTocRule>(text).getOrThrow() GSON.fromJsonObject<TxtTocRule>(text).getOrThrow()
} catch (e: Exception) { } catch (e: Exception) {
context.toastOnUi("格式不对") context.toastOnUi(R.string.invalid_format)
null null
} }
} }
} }
@@ -168,15 +168,17 @@ fun BackupConfigScreen(
) { uri -> ) { uri ->
uri?.let { uri?.let {
showLoadingDialog = true showLoadingDialog = true
loadingText = "恢复中…" loadingText = context.getString(R.string.on_restore)
scope.launch { scope.launch {
try { try {
Restore.restore(context, uri) Restore.restore(context, uri)
showLoadingDialog = false showLoadingDialog = false
snackbarHostState.showSnackbar("恢复成功") snackbarHostState.showSnackbar(context.getString(R.string.restore_success))
} catch (e: Exception) { } catch (e: Exception) {
showLoadingDialog = false showLoadingDialog = false
snackbarHostState.showSnackbar("恢复出错: ${e.localizedMessage}") snackbarHostState.showSnackbar(
context.getString(R.string.restore_fail_with_error, e.localizedMessage)
)
} }
} }
} }
@@ -360,20 +362,24 @@ fun BackupConfigScreen(
value = tempAccount, value = tempAccount,
onValueChange = { tempAccount = it }, onValueChange = { tempAccount = it },
backgroundColor = LegadoTheme.colorScheme.surface, backgroundColor = LegadoTheme.colorScheme.surface,
label = "账号" label = stringResource(R.string.web_dav_account)
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
AppTextField( AppTextField(
value = tempPassword, value = tempPassword,
onValueChange = { tempPassword = it }, onValueChange = { tempPassword = it },
backgroundColor = MiuixTheme.colorScheme.surface, backgroundColor = MiuixTheme.colorScheme.surface,
label = "密码", label = stringResource(R.string.web_dav_pw),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = { trailingIcon = {
val image = val image =
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
val description = if (passwordVisible) "隐藏密码" else "显示密码" val description = if (passwordVisible) {
stringResource(R.string.hide_password)
} else {
stringResource(R.string.show_password)
}
IconButton(onClick = { passwordVisible = !passwordVisible }) { IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, contentDescription = description) Icon(imageVector = image, contentDescription = description)
} }
@@ -387,13 +393,13 @@ fun BackupConfigScreen(
showWebDavAuthDialog = false showWebDavAuthDialog = false
scope.launch { scope.launch {
showLoadingDialog = true showLoadingDialog = true
loadingText = "测试中…" loadingText = context.getString(R.string.test_sync_loading_text)
val success = viewModel.testWebDav() val success = viewModel.testWebDav()
showLoadingDialog = false showLoadingDialog = false
if (success) { if (success) {
snackbarHostState.showSnackbar("WebDav 配置正确") snackbarHostState.showSnackbar(context.getString(R.string.test_sync_status_success))
} else { } else {
snackbarHostState.showSnackbar("WebDav 配置错误") snackbarHostState.showSnackbar(context.getString(R.string.test_sync_status_fail))
} }
} }
}, },
@@ -483,7 +489,7 @@ fun BackupConfigScreen(
) { ) {
OptionCard( OptionCard(
icon = Icons.Default.PhoneAndroid, icon = Icons.Default.PhoneAndroid,
text = "本地恢复", text = stringResource(R.string.restore_from_local),
onClick = { onClick = {
showRestoreOptionSheet = false showRestoreOptionSheet = false
restoreFileLauncher.launch(arrayOf("application/zip")) restoreFileLauncher.launch(arrayOf("application/zip"))
@@ -491,20 +497,23 @@ fun BackupConfigScreen(
) )
OptionCard( OptionCard(
icon = Icons.Default.Cloud, icon = Icons.Default.Cloud,
text = "网络恢复", text = stringResource(R.string.restore_from_network),
onClick = { onClick = {
showRestoreOptionSheet = false showRestoreOptionSheet = false
scope.launch { scope.launch {
showLoadingDialog = true showLoadingDialog = true
loadingText = "加载中" loadingText = context.getString(R.string.loading)
try { try {
val names = viewModel.getBackupNames() val names = viewModel.getBackupNames()
backupNames = names backupNames = names
showRestoreSheet = true showRestoreSheet = true
} catch (e: Exception) { } catch (e: Exception) {
confirmDialogTitle = "恢复" confirmDialogTitle = context.getString(R.string.restore)
confirmDialogText = confirmDialogText =
"WebDavError\n${e.localizedMessage}\n将从本地备份恢复。" context.getString(
R.string.webdav_restore_fallback_message,
e.localizedMessage
)
onConfirmAction = { onConfirmAction = {
restoreFileLauncher.launch(arrayOf("application/zip")) restoreFileLauncher.launch(arrayOf("application/zip"))
} }
@@ -535,19 +544,21 @@ fun BackupConfigScreen(
onToggleSelection = { onToggleSelection = {
showRestoreSheet = false showRestoreSheet = false
showLoadingDialog = true showLoadingDialog = true
loadingText = "恢复中…" loadingText = context.getString(R.string.on_restore)
viewModel.restoreWebDav( viewModel.restoreWebDav(
it, it,
{ {
showLoadingDialog = false showLoadingDialog = false
scope.launch { scope.launch {
snackbarHostState.showSnackbar("恢复成功") snackbarHostState.showSnackbar(context.getString(R.string.restore_success))
} }
}, },
{ error -> { error ->
showLoadingDialog = false showLoadingDialog = false
scope.launch { scope.launch {
snackbarHostState.showSnackbar("WebDav恢复出错\n$error") snackbarHostState.showSnackbar(
context.getString(R.string.webdav_restore_fail, error)
)
} }
} }
) )
@@ -2,6 +2,7 @@ package io.legado.app.ui.config.backupConfig
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.legado.app.R
import io.legado.app.domain.usecase.WebDavBackupUseCase import io.legado.app.domain.usecase.WebDavBackupUseCase
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -55,7 +56,7 @@ class BackupConfigViewModel(
} }
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onError(e.localizedMessage ?: "备份出错") onError(e.localizedMessage ?: appCtx.getString(R.string.backup_error))
} }
} }
} }
@@ -76,7 +77,7 @@ class BackupConfigViewModel(
} }
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onError(e.localizedMessage ?: "恢复出错") onError(e.localizedMessage ?: appCtx.getString(R.string.restore_error))
} }
} }
} }
@@ -134,9 +134,11 @@ fun CoverConfigScreen(
val coverCount = CoverConfig.defaultCover.split(",").filter { it.isNotBlank() }.size val coverCount = CoverConfig.defaultCover.split(",").filter { it.isNotBlank() }.size
ClickableSettingItem( ClickableSettingItem(
title = stringResource(R.string.default_cover), title = stringResource(R.string.default_cover),
description = if (coverCount > 0) "已选择 $coverCount 张图片" else stringResource( description = if (coverCount > 0) {
R.string.select_image stringResource(R.string.cover_selected_images_count, coverCount)
), } else {
stringResource(R.string.select_image)
},
onClick = { manageKey = PreferKey.defaultCover } onClick = { manageKey = PreferKey.defaultCover }
) )
@@ -191,9 +193,11 @@ fun CoverConfigScreen(
CoverConfig.defaultCoverDark.split(",").filter { it.isNotBlank() }.size CoverConfig.defaultCoverDark.split(",").filter { it.isNotBlank() }.size
ClickableSettingItem( ClickableSettingItem(
title = stringResource(R.string.default_cover), title = stringResource(R.string.default_cover),
description = if (coverCount > 0) "已选择 $coverCount 张图片" else stringResource( description = if (coverCount > 0) {
R.string.select_image stringResource(R.string.cover_selected_images_count, coverCount)
), } else {
stringResource(R.string.select_image)
},
onClick = { manageKey = PreferKey.defaultCoverDark } onClick = { manageKey = PreferKey.defaultCoverDark }
) )
@@ -76,7 +76,7 @@ fun CoverRuleConfigSheet(
imageVector = Icons.Default.Save, imageVector = Icons.Default.Save,
onClick = { onClick = {
if (searchUrl.isBlank() || coverRule.isBlank()) { if (searchUrl.isBlank() || coverRule.isBlank()) {
appCtx.toastOnUi("搜索url和cover规则不能为空") appCtx.toastOnUi(R.string.cover_rule_fields_required)
} else { } else {
val newConfig = ruleState?.copy( val newConfig = ruleState?.copy(
enable = enable, enable = enable,
@@ -72,7 +72,7 @@ fun CustomThemeScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = "自定义颜色", title = stringResource(R.string.custom_theme_colors),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopBarNavigationButton(onClick = onBackClick) TopBarNavigationButton(onClick = onBackClick)
@@ -91,7 +91,7 @@ fun CustomThemeScreen(
item { item {
SplicedColumnGroup { SplicedColumnGroup {
SwitchSettingItem( SwitchSettingItem(
title = "使用色板生成颜色", title = stringResource(R.string.theme_manage_use_palette_colors),
checked = !enableDeepPersonalization, checked = !enableDeepPersonalization,
onCheckedChange = { ThemeConfig.enableDeepPersonalization = !it } onCheckedChange = { ThemeConfig.enableDeepPersonalization = !it }
) )
@@ -104,7 +104,7 @@ fun CustomThemeScreen(
SplicedColumnGroup(title = stringResource(R.string.color_setting)) { SplicedColumnGroup(title = stringResource(R.string.color_setting)) {
// Primary colors // Primary colors
ClickableSettingItem( ClickableSettingItem(
title = "主题色", title = stringResource(R.string.theme_manage_primary_color),
option = if (themeColor != 0) "#${Integer.toHexString(themeColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (themeColor != 0) "#${Integer.toHexString(themeColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "themeColor" currentColorKey = "themeColor"
@@ -128,7 +128,7 @@ fun CustomThemeScreen(
) )
ClickableSettingItem( ClickableSettingItem(
title = "次要主题色", title = stringResource(R.string.theme_manage_secondary_color),
option = if (secondaryThemeColor != 0) "#${Integer.toHexString(secondaryThemeColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (secondaryThemeColor != 0) "#${Integer.toHexString(secondaryThemeColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "secondaryThemeColor" currentColorKey = "secondaryThemeColor"
@@ -152,7 +152,7 @@ fun CustomThemeScreen(
) )
ClickableSettingItem( ClickableSettingItem(
title = "主要字体色", title = stringResource(R.string.theme_manage_primary_text_color),
option = if (primaryTextColor != 0) "#${Integer.toHexString(primaryTextColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (primaryTextColor != 0) "#${Integer.toHexString(primaryTextColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "primaryTextColor" currentColorKey = "primaryTextColor"
@@ -176,7 +176,7 @@ fun CustomThemeScreen(
) )
ClickableSettingItem( ClickableSettingItem(
title = "次要字体色", title = stringResource(R.string.theme_manage_secondary_text_color),
option = if (secondaryTextColor != 0) "#${Integer.toHexString(secondaryTextColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (secondaryTextColor != 0) "#${Integer.toHexString(secondaryTextColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "secondaryTextColor" currentColorKey = "secondaryTextColor"
@@ -200,7 +200,7 @@ fun CustomThemeScreen(
) )
ClickableSettingItem( ClickableSettingItem(
title = "背景色", title = stringResource(R.string.theme_manage_background_color),
option = if (themeBackgroundColor != 0) "#${Integer.toHexString(themeBackgroundColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (themeBackgroundColor != 0) "#${Integer.toHexString(themeBackgroundColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "themeBackgroundColor" currentColorKey = "themeBackgroundColor"
@@ -224,7 +224,7 @@ fun CustomThemeScreen(
) )
ClickableSettingItem( ClickableSettingItem(
title = "标签容器色", title = stringResource(R.string.theme_manage_label_container_color),
option = if (labelContainerColor != 0) "#${Integer.toHexString(labelContainerColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (labelContainerColor != 0) "#${Integer.toHexString(labelContainerColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
currentColorKey = "labelContainerColor" currentColorKey = "labelContainerColor"
@@ -88,7 +88,7 @@ fun DirectLinkUploadBottomSheet(
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
) { ) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = "导入默认", text = stringResource(R.string.import_default_rule),
leadingIcon = { Icon(Icons.Default.Download, null) }, leadingIcon = { Icon(Icons.Default.Download, null) },
onClick = { onClick = {
showMenu = false showMenu = false
@@ -124,7 +124,7 @@ fun DirectLinkUploadBottomSheet(
viewModel.upView(rule) viewModel.upView(rule)
} }
}.onFailure { }.onFailure {
context.toastOnUi("剪贴板为空或格式不对") context.toastOnUi(R.string.clipboard_empty_or_invalid)
} }
} }
) )
@@ -186,7 +186,7 @@ fun DirectLinkUploadBottomSheet(
if (viewModel.saveDirectLinkRule()) { if (viewModel.saveDirectLinkRule()) {
onDismiss() onDismiss()
} else { } else {
context.toastOnUi("请填写完整信息") context.toastOnUi(R.string.complete_required_information)
} }
}, },
dismissText = stringResource(R.string.cancel), dismissText = stringResource(R.string.cancel),
@@ -147,7 +147,7 @@ fun OtherConfigScreen(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else { } else {
Toast.makeText(context, "无需申请", Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.permission_not_required, Toast.LENGTH_SHORT).show()
} }
} }
) )
@@ -97,7 +97,7 @@ fun BackgroundImageManageSheet(
) { ) {
AppIcon( AppIcon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
contentDescription = "Add", contentDescription = stringResource(R.string.add),
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
tint = LegadoTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
@@ -24,8 +24,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import io.legado.app.R
import io.legado.app.help.config.TagColorGenerator import io.legado.app.help.config.TagColorGenerator
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
@@ -53,7 +55,7 @@ fun LabelColorManageSheet(
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = "管理标签颜色", title = stringResource(R.string.theme_config_manage_label_colors),
startAction = { startAction = {
MediumOutlinedIconButton( MediumOutlinedIconButton(
onClick = { onClick = {
@@ -84,7 +86,7 @@ fun LabelColorManageSheet(
) { ) {
items(tagColors.size) { index -> items(tagColors.size) { index ->
val colorPair = tagColors[index] val colorPair = tagColors[index]
val label = "标签 ${index + 1}" val label = stringResource(R.string.theme_config_label_color_name, index + 1)
NormalCard( NormalCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
containerColor = LegadoTheme.colorScheme.onSheetContent containerColor = LegadoTheme.colorScheme.onSheetContent
@@ -2,6 +2,7 @@ package io.legado.app.ui.config.themeConfig
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -24,8 +25,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.NormalCard
@@ -36,7 +39,7 @@ import java.io.File
private data class NavIconDestination( private data class NavIconDestination(
val key: String, val key: String,
val label: String, @param:StringRes val labelRes: Int,
val path: String, val path: String,
val onSetPath: (String) -> Unit, val onSetPath: (String) -> Unit,
) )
@@ -51,10 +54,10 @@ fun NavIconManageSheet(
var activeDest by remember { mutableStateOf<String?>(null) } var activeDest by remember { mutableStateOf<String?>(null) }
val destinations = listOf( val destinations = listOf(
NavIconDestination("bookshelf", "书架", ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it }, NavIconDestination("bookshelf", R.string.bookshelf, ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it },
NavIconDestination("explore", "发现", ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it }, NavIconDestination("explore", R.string.discovery, ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it },
NavIconDestination("rss", "订阅", ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it }, NavIconDestination("rss", R.string.rss, ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it },
NavIconDestination("my", "我的", ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it }, NavIconDestination("my", R.string.my, ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it },
) )
val selectImage = rememberLauncherForActivityResult( val selectImage = rememberLauncherForActivityResult(
@@ -79,7 +82,7 @@ fun NavIconManageSheet(
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = "导航栏图标", title = stringResource(R.string.theme_config_nav_icons),
) { ) {
Column(modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp)) { Column(modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp)) {
Row( Row(
@@ -89,6 +92,7 @@ fun NavIconManageSheet(
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
destinations.forEach { dest -> destinations.forEach { dest ->
val label = stringResource(dest.labelRes)
Column( Column(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
@@ -108,7 +112,7 @@ fun NavIconManageSheet(
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
AsyncImage( AsyncImage(
model = dest.path, model = dest.path,
contentDescription = dest.label, contentDescription = label,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
@@ -128,7 +132,10 @@ fun NavIconManageSheet(
) { ) {
AppIcon( AppIcon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
contentDescription = "添加${dest.label}图标", contentDescription = stringResource(
R.string.theme_config_add_nav_icon,
label
),
modifier = Modifier.size(32.dp), modifier = Modifier.size(32.dp),
tint = LegadoTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
@@ -136,7 +143,7 @@ fun NavIconManageSheet(
} }
} }
AppText( AppText(
text = dest.label, text = label,
modifier = Modifier.padding(top = 8.dp), modifier = Modifier.padding(top = 8.dp),
) )
} }
@@ -227,7 +227,7 @@ fun ThemeConfigScreen(
contentColor = MiuixTheme.colorScheme.onPrimary contentColor = MiuixTheme.colorScheme.onPrimary
) )
) { ) {
MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!") MiuixText(stringResource(R.string.theme_config_miuix_experimental_warning))
} }
} }
@@ -420,8 +420,8 @@ fun ThemeConfigScreen(
onCheckedChange = { ThemeConfig.useFloatingBottomBarLiquidGlass = it } onCheckedChange = { ThemeConfig.useFloatingBottomBarLiquidGlass = it }
) )
SliderSettingItem( SliderSettingItem(
title = "底栏模糊强度", title = stringResource(R.string.theme_config_bottom_bar_lens_radius),
description = "控制液态玻璃的扭曲程度", description = stringResource(R.string.theme_config_bottom_bar_lens_radius_summary),
value = ThemeConfig.bottomBarLensRadius, value = ThemeConfig.bottomBarLensRadius,
defaultValue = 24f, defaultValue = 24f,
valueRange = 0f..50f, valueRange = 0f..50f,
@@ -483,30 +483,30 @@ fun ThemeConfigScreen(
} }
if (ThemeConfig.enableBlur) { if (ThemeConfig.enableBlur) {
SliderSettingItem( SliderSettingItem(
title = "顶栏模糊半径", title = stringResource(R.string.theme_manage_top_bar_blur_radius),
description = "模糊半径越大,系统运行越卡顿", description = stringResource(R.string.theme_config_blur_radius_performance_summary),
value = ThemeConfig.topBarBlurRadius.toFloat(), value = ThemeConfig.topBarBlurRadius.toFloat(),
defaultValue = 24f, defaultValue = 24f,
valueRange = 0f..30f, valueRange = 0f..30f,
onValueChange = { ThemeConfig.topBarBlurRadius = it.toInt() } onValueChange = { ThemeConfig.topBarBlurRadius = it.toInt() }
) )
SliderSettingItem( SliderSettingItem(
title = "底栏模糊半径", title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
description = "模糊半径越大,系统运行越卡顿", description = stringResource(R.string.theme_config_blur_radius_performance_summary),
value = ThemeConfig.bottomBarBlurRadius.toFloat(), value = ThemeConfig.bottomBarBlurRadius.toFloat(),
defaultValue = 8f, defaultValue = 8f,
valueRange = 0f..10f, valueRange = 0f..10f,
onValueChange = { ThemeConfig.bottomBarBlurRadius = it.toInt() } onValueChange = { ThemeConfig.bottomBarBlurRadius = it.toInt() }
) )
SliderSettingItem( SliderSettingItem(
title = "顶栏模糊透明度", title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
value = ThemeConfig.topBarBlurAlpha.toFloat(), value = ThemeConfig.topBarBlurAlpha.toFloat(),
defaultValue = 73f, defaultValue = 73f,
valueRange = 0f..100f, valueRange = 0f..100f,
onValueChange = { ThemeConfig.topBarBlurAlpha = it.toInt() } onValueChange = { ThemeConfig.topBarBlurAlpha = it.toInt() }
) )
SliderSettingItem( SliderSettingItem(
title = "底栏模糊透明度", title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
value = ThemeConfig.bottomBarBlurAlpha.toFloat(), value = ThemeConfig.bottomBarBlurAlpha.toFloat(),
defaultValue = 40f, defaultValue = 40f,
valueRange = 0f..100f, valueRange = 0f..100f,
@@ -610,15 +610,15 @@ fun ThemeConfigScreen(
// Container settings // Container settings
item { item {
SplicedColumnGroup(title = "容器设置") { SplicedColumnGroup(title = stringResource(R.string.theme_manage_section_container)) {
SwitchSettingItem( SwitchSettingItem(
title = "显示分割线", title = stringResource(R.string.show_divider_line),
checked = ThemeConfig.enableItemDivider, checked = ThemeConfig.enableItemDivider,
onCheckedChange = { ThemeConfig.enableItemDivider = it } onCheckedChange = { ThemeConfig.enableItemDivider = it }
) )
if (ThemeConfig.enableItemDivider) { if (ThemeConfig.enableItemDivider) {
SliderSettingItem( SliderSettingItem(
title = "分割线粗细", title = stringResource(R.string.theme_config_divider_width),
description = "${ThemeConfig.itemDividerWidth}dp", description = "${ThemeConfig.itemDividerWidth}dp",
value = ThemeConfig.itemDividerWidth, value = ThemeConfig.itemDividerWidth,
defaultValue = 1f, defaultValue = 1f,
@@ -627,7 +627,7 @@ fun ThemeConfigScreen(
onValueChange = { ThemeConfig.itemDividerWidth = it } onValueChange = { ThemeConfig.itemDividerWidth = it }
) )
SliderSettingItem( SliderSettingItem(
title = "分割线长度", title = stringResource(R.string.theme_config_divider_length),
description = "${ThemeConfig.itemDividerLength.toInt()}%", description = "${ThemeConfig.itemDividerLength.toInt()}%",
value = ThemeConfig.itemDividerLength, value = ThemeConfig.itemDividerLength,
defaultValue = 80f, defaultValue = 80f,
@@ -636,7 +636,7 @@ fun ThemeConfigScreen(
onValueChange = { ThemeConfig.itemDividerLength = it } onValueChange = { ThemeConfig.itemDividerLength = it }
) )
ClickableSettingItem( ClickableSettingItem(
title = "分割线颜色", title = stringResource(R.string.tip_divider_color),
option = if (ThemeConfig.itemDividerColor != 0) "#${Integer.toHexString(ThemeConfig.itemDividerColor).uppercase()}" else stringResource(R.string.click_to_select), option = if (ThemeConfig.itemDividerColor != 0) "#${Integer.toHexString(ThemeConfig.itemDividerColor).uppercase()}" else stringResource(R.string.click_to_select),
onClick = { onClick = {
showBorderColorPicker = true showBorderColorPicker = true
@@ -659,7 +659,7 @@ fun ThemeConfigScreen(
// Nav icon settings // Nav icon settings
item { item {
SplicedColumnGroup(title = "导航栏图标设置") { SplicedColumnGroup(title = stringResource(R.string.theme_config_nav_icon_settings)) {
val customCount = listOf( val customCount = listOf(
ThemeConfig.navIconBookshelf, ThemeConfig.navIconBookshelf,
ThemeConfig.navIconExplore, ThemeConfig.navIconExplore,
@@ -667,8 +667,12 @@ fun ThemeConfigScreen(
ThemeConfig.navIconMy ThemeConfig.navIconMy
).count { it.isNotEmpty() } ).count { it.isNotEmpty() }
ClickableSettingItem( ClickableSettingItem(
title = "导航栏图标", title = stringResource(R.string.theme_config_nav_icons),
description = if (customCount > 0) "已设置 $customCount 个自定义图标" else "使用默认图标", description = if (customCount > 0) {
stringResource(R.string.theme_config_nav_icons_custom_count, customCount)
} else {
stringResource(R.string.theme_config_nav_icons_default)
},
onClick = { showNavIconSheet = true } onClick = { showNavIconSheet = true }
) )
} }
@@ -676,10 +680,10 @@ fun ThemeConfigScreen(
// Theme management // Theme management
item { item {
SplicedColumnGroup(title = "主题管理") { SplicedColumnGroup(title = stringResource(R.string.theme_pack)) {
ClickableSettingItem( ClickableSettingItem(
title = "主题管理", title = stringResource(R.string.theme_pack),
description = "保存、导入、导出主题配置", description = stringResource(R.string.theme_pack_s),
onClick = onNavigateToThemeManage onClick = onNavigateToThemeManage
) )
} }
@@ -750,7 +754,7 @@ fun ThemeConfigScreen(
startAction = { startAction = {
SmallIconButton( SmallIconButton(
imageVector = Icons.Default.Delete, imageVector = Icons.Default.Delete,
contentDescription = "清除", contentDescription = stringResource(R.string.clear),
onClick = { onClick = {
ThemeConfig.appFontPath = null ThemeConfig.appFontPath = null
showFontSheet = false showFontSheet = false
@@ -760,7 +764,7 @@ fun ThemeConfigScreen(
endAction = { endAction = {
SmallIconButton( SmallIconButton(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
contentDescription = "选择文件夹", contentDescription = stringResource(R.string.select_folder),
onClick = { fontFolderLauncher.launch(null) } onClick = { fontFolderLauncher.launch(null) }
) )
}, },
@@ -771,7 +775,7 @@ fun ThemeConfigScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = "没有字体文件", text = stringResource(R.string.theme_config_no_font_files),
style = MaterialTheme.typography.bodyLarge style = MaterialTheme.typography.bodyLarge
) )
} }
@@ -79,4 +79,4 @@ class ThemeConfigViewModel : ViewModel() {
ThemeConfig.bgImageLight = newPath ThemeConfig.bgImageLight = newPath
} }
} }
} }
@@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.config.ThemeExportData import io.legado.app.help.config.ThemeExportData
@@ -58,7 +59,7 @@ fun EditThemeSheet(
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = "编辑主题", title = stringResource(R.string.theme_manage_edit_theme),
endAction = { endAction = {
MediumIconButton( MediumIconButton(
onClick = { onClick = {
@@ -92,30 +93,30 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Basic settings // Basic settings
SectionTitle("基础设置") SectionTitle(stringResource(R.string.theme_manage_section_basic))
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "主题模式", title = stringResource(R.string.theme_mode),
selectedValue = data.themeMode, selectedValue = data.themeMode,
displayEntries = stringArrayResource(R.array.theme_mode), displayEntries = stringArrayResource(R.array.theme_mode),
entryValues = stringArrayResource(R.array.theme_mode_v), entryValues = stringArrayResource(R.array.theme_mode_v),
onValueChange = { data = data.copy(themeMode = it) } onValueChange = { data = data.copy(themeMode = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "调色板风格", title = stringResource(R.string.palette_style),
selectedValue = data.paletteStyle, selectedValue = data.paletteStyle,
displayEntries = stringArrayResource(R.array.paletteStyle), displayEntries = stringArrayResource(R.array.paletteStyle),
entryValues = stringArrayResource(R.array.paletteStyle_value), entryValues = stringArrayResource(R.array.paletteStyle_value),
onValueChange = { data = data.copy(paletteStyle = it) } onValueChange = { data = data.copy(paletteStyle = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "Material 版本", title = stringResource(R.string.material_version),
selectedValue = data.materialVersion, selectedValue = data.materialVersion,
displayEntries = stringArrayResource(R.array.materialVersion), displayEntries = stringArrayResource(R.array.materialVersion),
entryValues = stringArrayResource(R.array.materialVersion_value), entryValues = stringArrayResource(R.array.materialVersion_value),
onValueChange = { data = data.copy(materialVersion = it) } onValueChange = { data = data.copy(materialVersion = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "对比度偏好", title = stringResource(R.string.preferred_contrast),
selectedValue = data.customContrast, selectedValue = data.customContrast,
displayEntries = stringArrayResource(R.array.customContrast), displayEntries = stringArrayResource(R.array.customContrast),
entryValues = stringArrayResource(R.array.customContrast_value), entryValues = stringArrayResource(R.array.customContrast_value),
@@ -125,36 +126,36 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Colors // Colors
SectionTitle("颜色设置") SectionTitle(stringResource(R.string.theme_manage_section_colors))
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "使用色板生成颜色", title = stringResource(R.string.theme_manage_use_palette_colors),
checked = !data.enableDeepPersonalization, checked = !data.enableDeepPersonalization,
onCheckedChange = { data = data.copy(enableDeepPersonalization = !it) } onCheckedChange = { data = data.copy(enableDeepPersonalization = !it) }
) )
if (data.enableDeepPersonalization) { if (data.enableDeepPersonalization) {
ColorItem("主题色", data.themeColor) { ColorItem(stringResource(R.string.theme_manage_primary_color), data.themeColor) {
currentColorKey = "themeColor"; showColorPicker = true currentColorKey = "themeColor"; showColorPicker = true
} }
ColorItem("次要主题色", data.secondaryThemeColor) { ColorItem(stringResource(R.string.theme_manage_secondary_color), data.secondaryThemeColor) {
currentColorKey = "secondaryThemeColor"; showColorPicker = true currentColorKey = "secondaryThemeColor"; showColorPicker = true
} }
ColorItem("主要字体色", data.primaryTextColor) { ColorItem(stringResource(R.string.theme_manage_primary_text_color), data.primaryTextColor) {
currentColorKey = "primaryTextColor"; showColorPicker = true currentColorKey = "primaryTextColor"; showColorPicker = true
} }
ColorItem("次要字体色", data.secondaryTextColor) { ColorItem(stringResource(R.string.theme_manage_secondary_text_color), data.secondaryTextColor) {
currentColorKey = "secondaryTextColor"; showColorPicker = true currentColorKey = "secondaryTextColor"; showColorPicker = true
} }
ColorItem("背景色", data.themeBackgroundColor) { ColorItem(stringResource(R.string.theme_manage_background_color), data.themeBackgroundColor) {
currentColorKey = "themeBackgroundColor"; showColorPicker = true currentColorKey = "themeBackgroundColor"; showColorPicker = true
} }
ColorItem("标签容器色", data.labelContainerColor) { ColorItem(stringResource(R.string.theme_manage_label_container_color), data.labelContainerColor) {
currentColorKey = "labelContainerColor"; showColorPicker = true currentColorKey = "labelContainerColor"; showColorPicker = true
} }
} else { } else {
ColorItem("日间种子色", data.cPrimary) { ColorItem(stringResource(R.string.theme_manage_day_seed_color), data.cPrimary) {
currentColorKey = "cPrimary"; showColorPicker = true currentColorKey = "cPrimary"; showColorPicker = true
} }
ColorItem("夜间种子色", data.cNPrimary) { ColorItem(stringResource(R.string.theme_manage_night_seed_color), data.cNPrimary) {
currentColorKey = "cNPrimary"; showColorPicker = true currentColorKey = "cNPrimary"; showColorPicker = true
} }
} }
@@ -162,9 +163,9 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Interface layout // Interface layout
SectionTitle("界面布局") SectionTitle(stringResource(R.string.theme_manage_section_layout))
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "发现", title = stringResource(R.string.theme_manage_show_discovery),
checked = data.showDiscovery, checked = data.showDiscovery,
onCheckedChange = { data = data.copy(showDiscovery = it) } onCheckedChange = { data = data.copy(showDiscovery = it) }
) )
@@ -174,41 +175,41 @@ fun EditThemeSheet(
onCheckedChange = { data = data.copy(showRss = it) } onCheckedChange = { data = data.copy(showRss = it) }
) )
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "显示底栏", title = stringResource(R.string.show_bottom_nav),
checked = data.showBottomView, checked = data.showBottomView,
onCheckedChange = { data = data.copy(showBottomView = it) } onCheckedChange = { data = data.copy(showBottomView = it) }
) )
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "浮动底栏", title = stringResource(R.string.floating_bottom_bar),
checked = data.useFloatingBottomBar, checked = data.useFloatingBottomBar,
onCheckedChange = { data = data.copy(useFloatingBottomBar = it) } onCheckedChange = { data = data.copy(useFloatingBottomBar = it) }
) )
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "状态栏", title = stringResource(R.string.theme_manage_status_bar),
checked = data.showStatusBar, checked = data.showStatusBar,
onCheckedChange = { data = data.copy(showStatusBar = it) } onCheckedChange = { data = data.copy(showStatusBar = it) }
) )
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "翻页动画", title = stringResource(R.string.theme_manage_page_turn_animation),
checked = data.swipeAnimation, checked = data.swipeAnimation,
onCheckedChange = { data = data.copy(swipeAnimation = it) } onCheckedChange = { data = data.copy(swipeAnimation = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "平板模式", title = stringResource(R.string.tabletInterface),
selectedValue = data.tabletInterface, selectedValue = data.tabletInterface,
displayEntries = stringArrayResource(R.array.tabletInterface), displayEntries = stringArrayResource(R.array.tabletInterface),
entryValues = stringArrayResource(R.array.tabletInterface_value), entryValues = stringArrayResource(R.array.tabletInterface_value),
onValueChange = { data = data.copy(tabletInterface = it) } onValueChange = { data = data.copy(tabletInterface = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "标签显示", title = stringResource(R.string.theme_manage_label_visibility),
selectedValue = data.labelVisibilityMode, selectedValue = data.labelVisibilityMode,
displayEntries = stringArrayResource(R.array.label_vis_mode), displayEntries = stringArrayResource(R.array.label_vis_mode),
entryValues = stringArrayResource(R.array.label_vis_mode_value), entryValues = stringArrayResource(R.array.label_vis_mode_value),
onValueChange = { data = data.copy(labelVisibilityMode = it) } onValueChange = { data = data.copy(labelVisibilityMode = it) }
) )
CompactDropdownSettingItem( CompactDropdownSettingItem(
title = "默认主页", title = stringResource(R.string.default_home_page),
selectedValue = data.defaultHomePage, selectedValue = data.defaultHomePage,
displayEntries = stringArrayResource(R.array.default_home_page), displayEntries = stringArrayResource(R.array.default_home_page),
entryValues = stringArrayResource(R.array.default_home_page_value), entryValues = stringArrayResource(R.array.default_home_page_value),
@@ -218,33 +219,33 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Blur // Blur
SectionTitle("模糊效果") SectionTitle(stringResource(R.string.theme_manage_section_blur))
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "启用模糊", title = stringResource(R.string.is_blur_enable),
checked = data.enableBlur, checked = data.enableBlur,
onCheckedChange = { data = data.copy(enableBlur = it) } onCheckedChange = { data = data.copy(enableBlur = it) }
) )
if (data.enableBlur) { if (data.enableBlur) {
CompactSliderSettingItem( CompactSliderSettingItem(
title = "顶栏模糊半径", title = stringResource(R.string.theme_manage_top_bar_blur_radius),
value = data.topBarBlurRadius.toFloat(), value = data.topBarBlurRadius.toFloat(),
valueRange = 1f..60f, valueRange = 1f..60f,
onValueChange = { data = data.copy(topBarBlurRadius = it.toInt()) } onValueChange = { data = data.copy(topBarBlurRadius = it.toInt()) }
) )
CompactSliderSettingItem( CompactSliderSettingItem(
title = "底栏模糊半径", title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
value = data.bottomBarBlurRadius.toFloat(), value = data.bottomBarBlurRadius.toFloat(),
valueRange = 1f..60f, valueRange = 1f..60f,
onValueChange = { data = data.copy(bottomBarBlurRadius = it.toInt()) } onValueChange = { data = data.copy(bottomBarBlurRadius = it.toInt()) }
) )
CompactSliderSettingItem( CompactSliderSettingItem(
title = "顶栏透明度", title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
value = data.topBarBlurAlpha.toFloat(), value = data.topBarBlurAlpha.toFloat(),
valueRange = 0f..255f, valueRange = 0f..255f,
onValueChange = { data = data.copy(topBarBlurAlpha = it.toInt()) } onValueChange = { data = data.copy(topBarBlurAlpha = it.toInt()) }
) )
CompactSliderSettingItem( CompactSliderSettingItem(
title = "底栏透明度", title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
value = data.bottomBarBlurAlpha.toFloat(), value = data.bottomBarBlurAlpha.toFloat(),
valueRange = 0f..255f, valueRange = 0f..255f,
onValueChange = { data = data.copy(bottomBarBlurAlpha = it.toInt()) } onValueChange = { data = data.copy(bottomBarBlurAlpha = it.toInt()) }
@@ -254,15 +255,15 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Opacity // Opacity
SectionTitle("透明度") SectionTitle(stringResource(R.string.theme_manage_section_opacity))
CompactSliderSettingItem( CompactSliderSettingItem(
title = "顶栏透明度", title = stringResource(R.string.top_bar_opacity),
value = data.topBarOpacity.toFloat(), value = data.topBarOpacity.toFloat(),
valueRange = 0f..100f, valueRange = 0f..100f,
onValueChange = { data = data.copy(topBarOpacity = it.toInt()) } onValueChange = { data = data.copy(topBarOpacity = it.toInt()) }
) )
CompactSliderSettingItem( CompactSliderSettingItem(
title = "底栏透明度", title = stringResource(R.string.bottom_bar_opacity),
value = data.bottomBarOpacity.toFloat(), value = data.bottomBarOpacity.toFloat(),
valueRange = 0f..100f, valueRange = 0f..100f,
onValueChange = { data = data.copy(bottomBarOpacity = it.toInt()) } onValueChange = { data = data.copy(bottomBarOpacity = it.toInt()) }
@@ -271,9 +272,9 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Container // Container
SectionTitle("容器设置") SectionTitle(stringResource(R.string.theme_manage_section_container))
CompactSliderSettingItem( CompactSliderSettingItem(
title = "容器不透明度", title = stringResource(R.string.container_opacity),
value = data.containerOpacity.toFloat(), value = data.containerOpacity.toFloat(),
valueRange = 0f..100f, valueRange = 0f..100f,
onValueChange = { data = data.copy(containerOpacity = it.toInt()) } onValueChange = { data = data.copy(containerOpacity = it.toInt()) }
@@ -282,14 +283,14 @@ fun EditThemeSheet(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Other // Other
SectionTitle("其他") SectionTitle(stringResource(R.string.other))
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "纯黑模式", title = stringResource(R.string.pure_black),
checked = data.isPureBlack, checked = data.isPureBlack,
onCheckedChange = { data = data.copy(isPureBlack = it) } onCheckedChange = { data = data.copy(isPureBlack = it) }
) )
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "弹性顶栏", title = stringResource(R.string.use_flexible_top_bar),
checked = data.useFlexibleTopAppBar, checked = data.useFlexibleTopAppBar,
onCheckedChange = { data = data.copy(useFlexibleTopAppBar = it) } onCheckedChange = { data = data.copy(useFlexibleTopAppBar = it) }
) )
@@ -83,14 +83,14 @@ fun ThemeManageScreen(
if (target != null) { if (target != null) {
exportTarget = null exportTarget = null
if (ThemeImportExport.exportSavedThemeToFile(context, target, it)) { if (ThemeImportExport.exportSavedThemeToFile(context, target, it)) {
context.toastOnUi("主题导出成功") context.toastOnUi(R.string.theme_manage_export_success)
} else { } else {
context.toastOnUi("主题导出失败") context.toastOnUi(R.string.theme_manage_export_failed)
} }
} else if (ThemeImportExport.exportToFile(context, it)) { } else if (ThemeImportExport.exportToFile(context, it)) {
context.toastOnUi("主题导出成功") context.toastOnUi(R.string.theme_manage_export_success)
} else { } else {
context.toastOnUi("主题导出失败") context.toastOnUi(R.string.theme_manage_export_failed)
} }
} }
} }
@@ -100,10 +100,10 @@ fun ThemeManageScreen(
) { uri -> ) { uri ->
uri?.let { uri?.let {
if (ThemeImportExport.importFromUri(context, it)) { if (ThemeImportExport.importFromUri(context, it)) {
context.toastOnUi("主题导入成功,部分设置需要重启生效") context.toastOnUi(R.string.theme_manage_import_success)
showRestartDialog = true showRestartDialog = true
} else { } else {
context.toastOnUi("主题导入失败") context.toastOnUi(R.string.theme_manage_import_failed)
} }
} }
} }
@@ -112,7 +112,7 @@ fun ThemeManageScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = "主题管理", title = stringResource(R.string.theme_pack),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
TopBarNavigationButton(onClick = onBackClick) TopBarNavigationButton(onClick = onBackClick)
@@ -133,24 +133,24 @@ fun ThemeManageScreen(
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
SplicedColumnGroup { SplicedColumnGroup {
ClickableSettingItem( ClickableSettingItem(
title = "保存当前设置", title = stringResource(R.string.theme_manage_save_current),
description = "保存当前主题配置为新主题", description = stringResource(R.string.theme_manage_save_current_summary),
onClick = { onClick = {
newThemeName = "" newThemeName = ""
showSaveDialog = true showSaveDialog = true
} }
) )
ClickableSettingItem( ClickableSettingItem(
title = "导出当前主题", title = stringResource(R.string.theme_manage_export_current),
description = "将当前主题配置导出为JSON文件", description = stringResource(R.string.theme_manage_export_current_summary),
onClick = { onClick = {
exportTarget = null exportTarget = null
exportLauncher.launch("legado_theme_${System.currentTimeMillis()}.json") exportLauncher.launch("legado_theme_${System.currentTimeMillis()}.json")
} }
) )
ClickableSettingItem( ClickableSettingItem(
title = "导入主题配置", title = stringResource(R.string.theme_manage_import_config),
description = "从JSON文件导入主题配置", description = stringResource(R.string.theme_manage_import_config_summary),
onClick = { onClick = {
importLauncher.launch(arrayOf("application/json")) importLauncher.launch(arrayOf("application/json"))
} }
@@ -161,7 +161,7 @@ fun ThemeManageScreen(
if (savedThemes.isNotEmpty()) { if (savedThemes.isNotEmpty()) {
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }) {
AppText( AppText(
text = "已保存的主题", text = stringResource(R.string.theme_manage_saved_themes),
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
@@ -207,8 +207,8 @@ fun ThemeManageScreen(
AppAlertDialog( AppAlertDialog(
show = showSaveDialog, show = showSaveDialog,
onDismissRequest = { showSaveDialog = false }, onDismissRequest = { showSaveDialog = false },
title = "保存主题", title = stringResource(R.string.theme_manage_save_theme),
confirmText = "保存", confirmText = stringResource(R.string.theme_manage_save),
onConfirm = { onConfirm = {
if (newThemeName.isNotBlank()) { if (newThemeName.isNotBlank()) {
ThemeImportExport.saveCurrentAsTheme(newThemeName) ThemeImportExport.saveCurrentAsTheme(newThemeName)
@@ -216,13 +216,13 @@ fun ThemeManageScreen(
showSaveDialog = false showSaveDialog = false
} }
}, },
dismissText = "取消", dismissText = stringResource(R.string.cancel),
onDismiss = { showSaveDialog = false }, onDismiss = { showSaveDialog = false },
content = { content = {
AppTextField( AppTextField(
value = newThemeName, value = newThemeName,
onValueChange = { newThemeName = it }, onValueChange = { newThemeName = it },
placeholder = { AppText(text = "请输入主题名称") }, placeholder = { AppText(text = stringResource(R.string.theme_manage_name_hint)) },
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -233,8 +233,8 @@ fun ThemeManageScreen(
AppAlertDialog( AppAlertDialog(
show = applyTarget != null, show = applyTarget != null,
onDismissRequest = { applyTarget = null }, onDismissRequest = { applyTarget = null },
title = "应用主题", title = stringResource(R.string.theme_manage_apply_theme),
confirmText = "应用", confirmText = stringResource(R.string.theme_manage_apply),
onConfirm = { onConfirm = {
applyTarget?.let { theme -> applyTarget?.let { theme ->
ThemeImportExport.applySavedTheme(theme) ThemeImportExport.applySavedTheme(theme)
@@ -242,17 +242,17 @@ fun ThemeManageScreen(
} }
applyTarget = null applyTarget = null
}, },
dismissText = "取消", dismissText = stringResource(R.string.cancel),
onDismiss = { applyTarget = null }, onDismiss = { applyTarget = null },
text = "确定应用主题「${applyTarget?.name}」?应用后需要重启才能完全生效。" text = stringResource(R.string.theme_manage_apply_message, applyTarget?.name.orEmpty())
) )
// Delete theme dialog // Delete theme dialog
AppAlertDialog( AppAlertDialog(
show = deleteTarget != null, show = deleteTarget != null,
onDismissRequest = { deleteTarget = null }, onDismissRequest = { deleteTarget = null },
title = "删除主题", title = stringResource(R.string.theme_manage_delete_theme),
confirmText = "删除", confirmText = stringResource(R.string.delete),
onConfirm = { onConfirm = {
deleteTarget?.let { theme -> deleteTarget?.let { theme ->
ThemeImportExport.deleteSavedTheme(theme) ThemeImportExport.deleteSavedTheme(theme)
@@ -260,9 +260,9 @@ fun ThemeManageScreen(
} }
deleteTarget = null deleteTarget = null
}, },
dismissText = "取消", dismissText = stringResource(R.string.cancel),
onDismiss = { deleteTarget = null }, onDismiss = { deleteTarget = null },
text = "确定删除主题「${deleteTarget?.name}」?此操作不可恢复。" text = stringResource(R.string.theme_manage_delete_message, deleteTarget?.name.orEmpty())
) )
// Edit theme sheet // Edit theme sheet
@@ -323,7 +323,7 @@ private fun SavedThemeItem(
.background(lightBg) .background(lightBg)
) { ) {
AppText( AppText(
text = "日间", text = stringResource(R.string.theme_manage_preview_day),
style = MaterialTheme.typography.labelMediumEmphasized, style = MaterialTheme.typography.labelMediumEmphasized,
color = if (theme.data.primaryTextColor != 0) Color(theme.data.primaryTextColor).copy(alpha = 0.6f) color = if (theme.data.primaryTextColor != 0) Color(theme.data.primaryTextColor).copy(alpha = 0.6f)
else Color.Black.copy(alpha = 0.5f), else Color.Black.copy(alpha = 0.5f),
@@ -355,7 +355,7 @@ private fun SavedThemeItem(
.background(darkBg) .background(darkBg)
) { ) {
AppText( AppText(
text = "夜间", text = stringResource(R.string.theme_manage_preview_night),
style = MaterialTheme.typography.labelMediumEmphasized, style = MaterialTheme.typography.labelMediumEmphasized,
color = Color.White.copy(alpha = 0.5f), color = Color.White.copy(alpha = 0.5f),
modifier = Modifier modifier = Modifier
@@ -41,7 +41,9 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -193,7 +195,7 @@ fun AppFloatingActionButtonMenu(
tint = Color.White tint = Color.White
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text("菜单", color = Color.White) Text(stringResource(R.string.menu), color = Color.White)
} }
} }
} }
@@ -15,8 +15,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme.composeEngine import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -57,10 +59,10 @@ fun AppPullToRefresh(
contentPadding = PaddingValues(top = topPadding + 16.dp), contentPadding = PaddingValues(top = topPadding + 16.dp),
pullToRefreshState = miuixRememberPullToRefreshState(), pullToRefreshState = miuixRememberPullToRefreshState(),
refreshTexts = listOf( refreshTexts = listOf(
"下拉刷新", stringResource(R.string.pull_to_refresh),
"松开刷新", stringResource(R.string.release_to_refresh),
"正在刷新", stringResource(R.string.refreshing),
"正在刷新" stringResource(R.string.refreshing)
) )
) { ) {
content() content()
@@ -15,9 +15,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
@@ -83,11 +85,15 @@ fun CollapsibleHeader(
if (showIcon) { if (showIcon) {
Icon( Icon(
imageVector = Icons.Default.ExpandMore, imageVector = Icons.Default.ExpandMore,
contentDescription = if (isCollapsed) "展开" else "折叠", contentDescription = if (isCollapsed) {
stringResource(R.string.expand)
} else {
stringResource(R.string.collapse)
},
modifier = Modifier.rotate(rotation), modifier = Modifier.rotate(rotation),
tint = LegadoTheme.colorScheme.primary tint = LegadoTheme.colorScheme.primary
) )
} }
} }
} }
} }
@@ -70,7 +70,7 @@ fun ColorPickerSheet(
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = "选择颜色", title = stringResource(R.string.select_color),
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -123,7 +123,7 @@ fun ColorPickerSheet(
} }
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
label = "色值", label = stringResource(R.string.color_value),
singleLine = true, singleLine = true,
isError = isHexInputError, isError = isHexInputError,
backgroundColor = LegadoTheme.colorScheme.surface, backgroundColor = LegadoTheme.colorScheme.surface,
+142
View File
@@ -37,6 +37,7 @@
<string name="predictive_back_summary">启用系统预见性返回手势</string> <string name="predictive_back_summary">启用系统预见性返回手势</string>
<string name="restart_to_apply">重启以应用</string> <string name="restart_to_apply">重启以应用</string>
<string name="custom_theme">自定义主题</string> <string name="custom_theme">自定义主题</string>
<string name="custom_theme_colors">自定义颜色</string>
<string name="appearance_setting">外观设置</string> <string name="appearance_setting">外观设置</string>
<string name="click_to_select">点击选择</string> <string name="click_to_select">点击选择</string>
<string name="compose_related">Compose 相关</string> <string name="compose_related">Compose 相关</string>
@@ -106,6 +107,16 @@
<string name="origin_format">来源:%s</string> <string name="origin_format">来源:%s</string>
<string name="read_dur_progress">最近:%s</string> <string name="read_dur_progress">最近:%s</string>
<string name="book_name">书名</string> <string name="book_name">书名</string>
<string name="cover_url">封面链接</string>
<string name="fixed_book_type">固定书籍类型</string>
<string name="fixed_book_type_summary">书籍更新后不覆盖书籍类型</string>
<string name="book_type_label">书籍类型</string>
<string name="book_type_text">文本</string>
<string name="book_type_audio">音频</string>
<string name="book_type_image">图片</string>
<string name="add_tag">添加标签</string>
<string name="edit_tag">编辑标签</string>
<string name="tag">标签</string>
<string name="lasted_show">最新 · %s</string> <string name="lasted_show">最新 · %s</string>
<string name="check_add_bookshelf">是否将《%s》放入书架?</string> <string name="check_add_bookshelf">是否将《%s》放入书架?</string>
<string name="import_books_count">共 %s 个文本文件</string> <string name="import_books_count">共 %s 个文本文件</string>
@@ -130,6 +141,8 @@
<string name="pt_auto_download">自动下载最新章节</string> <string name="pt_auto_download">自动下载最新章节</string>
<string name="ps_auto_download">更新书籍时自动下载最新章节</string> <string name="ps_auto_download">更新书籍时自动下载最新章节</string>
<string name="backup_restore">备份与恢复</string> <string name="backup_restore">备份与恢复</string>
<string name="backup_error">备份出错</string>
<string name="restore_error">恢复出错</string>
<string name="web_dav_set">WebDav 设置</string> <string name="web_dav_set">WebDav 设置</string>
<string name="web_dav_set_import_old">WebDav 设置 • 导入旧版本数据</string> <string name="web_dav_set_import_old">WebDav 设置 • 导入旧版本数据</string>
<string name="backup">备份</string> <string name="backup">备份</string>
@@ -147,6 +160,11 @@
<string name="on_restore">正在恢复</string> <string name="on_restore">正在恢复</string>
<string name="restore_success">恢复成功</string> <string name="restore_success">恢复成功</string>
<string name="restore_fail">恢复失败</string> <string name="restore_fail">恢复失败</string>
<string name="restore_fail_with_error">恢复出错:%1$s</string>
<string name="restore_from_local">本地恢复</string>
<string name="restore_from_network">网络恢复</string>
<string name="webdav_restore_fallback_message">WebDAV 出错\n%1$s\n将从本地备份恢复。</string>
<string name="webdav_restore_fail">WebDAV 恢复出错\n%1$s</string>
<string name="screen_direction">屏幕方向</string> <string name="screen_direction">屏幕方向</string>
<string name="screen_sensor">跟随传感器</string> <string name="screen_sensor">跟随传感器</string>
<string name="screen_landscape">横向</string> <string name="screen_landscape">横向</string>
@@ -170,6 +188,13 @@
<string name="audio_pause">播放暂停</string> <string name="audio_pause">播放暂停</string>
<string name="text_return">返回</string> <string name="text_return">返回</string>
<string name="refresh">刷新</string> <string name="refresh">刷新</string>
<string name="pull_to_refresh">下拉刷新</string>
<string name="release_to_refresh">松开刷新</string>
<string name="refreshing">正在刷新</string>
<string name="expand">展开</string>
<string name="collapse">折叠</string>
<string name="select_color">选择颜色</string>
<string name="color_value">色值</string>
<string name="start">开始</string> <string name="start">开始</string>
<string name="stop">停止</string> <string name="stop">停止</string>
<string name="pause">暂停</string> <string name="pause">暂停</string>
@@ -645,6 +670,8 @@
<string name="tts">TTS</string> <string name="tts">TTS</string>
<string name="web_dav_pw">WebDav 密码</string> <string name="web_dav_pw">WebDav 密码</string>
<string name="web_dav_pw_s">输入你的 WebDav 授权密码</string> <string name="web_dav_pw_s">输入你的 WebDav 授权密码</string>
<string name="show_password">显示密码</string>
<string name="hide_password">隐藏密码</string>
<string name="web_dav_url_s">输入你的WebDav服务器地址</string> <string name="web_dav_url_s">输入你的WebDav服务器地址</string>
<string name="web_dav_url">WebDav 服务器地址</string> <string name="web_dav_url">WebDav 服务器地址</string>
<string name="web_dav_account">WebDav 账号</string> <string name="web_dav_account">WebDav 账号</string>
@@ -945,6 +972,10 @@
<string name="pref_anti_alias_summary">绘制图片时抗锯齿</string> <string name="pref_anti_alias_summary">绘制图片时抗锯齿</string>
<string name="upload_url">上传 URL</string> <string name="upload_url">上传 URL</string>
<string name="download_url_rule">下载 URL 规则</string> <string name="download_url_rule">下载 URL 规则</string>
<string name="clipboard_empty">剪贴板没有内容</string>
<string name="clipboard_empty_or_invalid">剪贴板为空或格式不正确</string>
<string name="invalid_format">格式不正确</string>
<string name="complete_required_information">请填写完整信息</string>
<string name="sort_by_respondTime">响应时间排序</string> <string name="sort_by_respondTime">响应时间排序</string>
<string name="respondTime">响应时间:%1$d ms</string> <string name="respondTime">响应时间:%1$d ms</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
@@ -1040,6 +1071,8 @@
<string name="search_via_url">搜索URL</string> <string name="search_via_url">搜索URL</string>
<string name="cover_rule_edit">封面规则</string> <string name="cover_rule_edit">封面规则</string>
<string name="cover_rule_summary">进入详情页时使用封面规则重新获取封面</string> <string name="cover_rule_summary">进入详情页时使用封面规则重新获取封面</string>
<string name="cover_rule_fields_required">搜索 URL 和封面规则不能为空</string>
<string name="cover_selected_images_count">已选择 %1$d 张图片</string>
<string name="scroll_to_dur_source">定位到当前书源</string> <string name="scroll_to_dur_source">定位到当前书源</string>
<string name="sys_tts_config">系统 TTS 设置</string> <string name="sys_tts_config">系统 TTS 设置</string>
<string name="sys_tts_config_summary">打开系统 TTS 设置界面</string> <string name="sys_tts_config_summary">打开系统 TTS 设置界面</string>
@@ -1137,6 +1170,7 @@
<string name="same_title_removed">移除重复标题</string> <string name="same_title_removed">移除重复标题</string>
<string name="update_book_fail">更新失败</string> <string name="update_book_fail">更新失败</string>
<string name="notification_permission_rationale">阅读需要发送通知来显示朗读控制和下载进度。</string> <string name="notification_permission_rationale">阅读需要发送通知来显示朗读控制和下载进度。</string>
<string name="permission_not_required">无需申请</string>
<string name="webdav_after_local_restore_confirm">WebDav 书源比本地新,是否恢复</string> <string name="webdav_after_local_restore_confirm">WebDav 书源比本地新,是否恢复</string>
<string name="c_whitelist">白名单(contentWhitelist)</string> <string name="c_whitelist">白名单(contentWhitelist)</string>
<string name="c_blacklist">黑名单(contentBlacklist)</string> <string name="c_blacklist">黑名单(contentBlacklist)</string>
@@ -1223,6 +1257,23 @@
<string name="key_page_on_long_press">按键长按翻页</string> <string name="key_page_on_long_press">按键长按翻页</string>
<string name="save_log">保存日志</string> <string name="save_log">保存日志</string>
<string name="create_heap_dump">创建堆转储</string> <string name="create_heap_dump">创建堆转储</string>
<string name="about_privacy_policy_title">隐私政策</string>
<string name="about_license_title">许可证</string>
<string name="about_disclaimer_title">免责声明</string>
<string name="about_installed_version_title">当前版本</string>
<string name="about_current_version">当前版本</string>
<string name="about_new_version">新版本</string>
<string name="about_update_channel">渠道</string>
<string name="about_update_action">更新</string>
<string name="about_backup_dir_not_set">未设置备份目录</string>
<string name="about_log_recording_disabled">未开启日志记录,请去其他设置里打开记录日志</string>
<string name="about_saved_to_backup_dir">已保存至备份目录</string>
<string name="about_heap_dump_recording_disabled">未开启堆转储记录,请去其他设置里打开记录堆转储</string>
<string name="about_heap_dump_creating">开始创建堆转储</string>
<string name="about_heap_dump_not_found">未找到堆转储文件</string>
<string name="about_download_info_incomplete">下载信息不完整</string>
<string name="about_no_data">没有数据</string>
<string name="about_start_download">开始下载: %s</string>
<string name="crash_report">崩溃报告</string> <string name="crash_report">崩溃报告</string>
<string name="crash_report_message">应用发生崩溃。报告问题时,请复制以下错误信息。</string> <string name="crash_report_message">应用发生崩溃。报告问题时,请复制以下错误信息。</string>
<string name="crash_report_empty">未找到崩溃信息。</string> <string name="crash_report_empty">未找到崩溃信息。</string>
@@ -1284,6 +1335,65 @@
<string name="ungroup">未分组</string> <string name="ungroup">未分组</string>
<string name="dynamic_colors">动态取色</string> <string name="dynamic_colors">动态取色</string>
<string name="dynamic_colors_summary">使用基于壁纸的主题颜色</string> <string name="dynamic_colors_summary">使用基于壁纸的主题颜色</string>
<string name="theme_manage_export_success">主题导出成功</string>
<string name="theme_manage_export_failed">主题导出失败</string>
<string name="theme_manage_import_success">主题导入成功,部分设置需要重启生效</string>
<string name="theme_manage_import_failed">主题导入失败</string>
<string name="theme_manage_save_current">保存当前设置</string>
<string name="theme_manage_save_current_summary">保存当前主题配置为新主题</string>
<string name="theme_manage_export_current">导出当前主题</string>
<string name="theme_manage_export_current_summary">将当前主题配置导出为 JSON 文件</string>
<string name="theme_manage_import_config">导入主题配置</string>
<string name="theme_manage_import_config_summary">从 JSON 文件导入主题配置</string>
<string name="theme_manage_saved_themes">已保存的主题</string>
<string name="theme_manage_save_theme">保存主题</string>
<string name="theme_manage_save">保存</string>
<string name="theme_manage_name_hint">请输入主题名称</string>
<string name="theme_manage_apply_theme">应用主题</string>
<string name="theme_manage_apply">应用</string>
<string name="theme_manage_apply_message">确定应用主题「%s」?应用后需要重启才能完全生效。</string>
<string name="theme_manage_delete_theme">删除主题</string>
<string name="theme_manage_delete_message">确定删除主题「%s」?此操作不可恢复。</string>
<string name="theme_manage_preview_day">日间</string>
<string name="theme_manage_preview_night">夜间</string>
<string name="theme_manage_edit_theme">编辑主题</string>
<string name="theme_manage_section_basic">基础设置</string>
<string name="theme_manage_section_colors">颜色设置</string>
<string name="theme_manage_use_palette_colors">使用色板生成颜色</string>
<string name="theme_manage_primary_color">主题色</string>
<string name="theme_manage_secondary_color">次要主题色</string>
<string name="theme_manage_primary_text_color">主要字体色</string>
<string name="theme_manage_secondary_text_color">次要字体色</string>
<string name="theme_manage_background_color">背景色</string>
<string name="theme_manage_label_container_color">标签容器色</string>
<string name="theme_manage_day_seed_color">日间种子色</string>
<string name="theme_manage_night_seed_color">夜间种子色</string>
<string name="theme_manage_section_layout">界面布局</string>
<string name="theme_manage_show_discovery">发现</string>
<string name="theme_manage_status_bar">状态栏</string>
<string name="theme_manage_page_turn_animation">翻页动画</string>
<string name="theme_manage_label_visibility">标签显示</string>
<string name="theme_manage_section_blur">模糊效果</string>
<string name="theme_manage_top_bar_blur_radius">顶栏模糊半径</string>
<string name="theme_manage_bottom_bar_blur_radius">底栏模糊半径</string>
<string name="theme_manage_top_bar_blur_opacity">顶栏模糊不透明度</string>
<string name="theme_manage_bottom_bar_blur_opacity">底栏模糊不透明度</string>
<string name="theme_manage_section_opacity">透明度</string>
<string name="theme_manage_section_container">容器设置</string>
<string name="theme_config_manage_label_colors">管理标签颜色</string>
<string name="theme_config_label_color_name">标签 %1$d</string>
<string name="theme_config_miuix_experimental_warning">Miuix 目前为测试主题,且不对基于 View 的界面生效。</string>
<string name="theme_config_bottom_bar_lens_radius">底栏模糊强度</string>
<string name="theme_config_bottom_bar_lens_radius_summary">控制液态玻璃的扭曲程度</string>
<string name="theme_config_blur_radius_performance_summary">模糊半径越大,系统运行越卡顿</string>
<string name="theme_config_divider_width">分割线粗细</string>
<string name="theme_config_divider_length">分割线长度</string>
<string name="theme_config_nav_icon_settings">导航栏图标设置</string>
<string name="theme_config_nav_icons">导航栏图标</string>
<string name="theme_config_nav_icons_custom_count">已设置 %1$d 个自定义图标</string>
<string name="theme_config_nav_icons_default">使用默认图标</string>
<string name="theme_config_add_nav_icon">添加%1$s图标</string>
<string name="theme_config_no_font_files">没有字体文件</string>
<string name="input_value_range">输入数值(%1$d-%2$d)</string> <string name="input_value_range">输入数值(%1$d-%2$d)</string>
<string name="change_icon_success">图标已修改</string> <string name="change_icon_success">图标已修改</string>
<string name="replace_rule_title_setting">配置替换规则</string> <string name="replace_rule_title_setting">配置替换规则</string>
@@ -1331,6 +1441,38 @@
<string name="abi_arm64_v8a">ABI: arm64-v8a</string> <string name="abi_arm64_v8a">ABI: arm64-v8a</string>
<string name="download_link">下载链接</string> <string name="download_link">下载链接</string>
<string name="no_bookmark">暂无书签</string> <string name="no_bookmark">暂无书签</string>
<string name="bookmark_management">书签管理</string>
<string name="locate_current_reading">定位至当前阅读</string>
<string name="move_to_top">移至顶部</string>
<string name="move_to_bottom">移至底部</string>
<string name="invert_selection">反选</string>
<string name="select_following">选择后续</string>
<string name="search_chapters">搜索章节…</string>
<string name="use_replace_rule">使用替换规则</string>
<string name="show_word_count">显示字数</string>
<string name="local_book_options">本地书籍选项</string>
<string name="local_book_toc_rule">本地书籍目录规则</string>
<string name="select_toc_rule">选择目录规则</string>
<string name="split_long_chapters">拆分超长章节</string>
<string name="export_bookmarks_json">导出书签为 JSON</string>
<string name="export_bookmarks_markdown">导出书签为 Markdown</string>
<string name="volume_management">卷管理</string>
<string name="quick_jump">快速跳转</string>
<string name="download_selected_count">下载已选(%1$d)</string>
<string name="toc_rule_update_failed">更新目录规则失败:%1$s</string>
<string name="toc_rule_updated">目录规则已更新</string>
<string name="setting_failed">设置失败:%1$s</string>
<string name="split_long_chapters_enabled">已开启长章节拆分</string>
<string name="split_long_chapters_disabled">已关闭长章节拆分</string>
<string name="no_bookmarks_to_export">没有可导出的书签</string>
<string name="save_success">保存成功</string>
<string name="save_failed_with_error">保存失败:%1$s</string>
<string name="select_chapters">请选择章节</string>
<string name="bookmarks_added_count">已添加 %1$d 个书签</string>
<string name="start_downloading_chapters">开始下载 %1$d 个章节</string>
<string name="start_downloading_chapter">开始下载章节</string>
<string name="all_chapters_cached">所有章节已缓存</string>
<string name="start_downloading_remaining_chapters">开始下载剩余 %1$d 个章节</string>
<string name="read_end">已读完</string> <string name="read_end">已读完</string>
<string name="system_engine">系统引擎</string> <string name="system_engine">系统引擎</string>
<string name="copy_from">剪切板导入</string> <string name="copy_from">剪切板导入</string>
+8 -8
View File
@@ -13,17 +13,17 @@
</string-array> </string-array>
<string-array name="layout_type"> <string-array name="layout_type">
<item>列表</item> <item>List</item>
<item>单列</item> <item>Single Column</item>
<item>双列</item> <item>Two Columns</item>
<item>瀑布</item> <item>Waterfall</item>
<item>三列</item> <item>Three Columns</item>
</string-array> </string-array>
<string-array name="rss_type"> <string-array name="rss_type">
<item>网页</item> <item>Web Page</item>
<item>图片</item> <item>Image</item>
<item>视频</item> <item>Video</item>
</string-array> </string-array>
<string-array name="group_style"> <string-array name="group_style">
+261 -119
View File
@@ -38,7 +38,8 @@
<string name="predictive_back_summary">Enable system predictive back gestures</string> <string name="predictive_back_summary">Enable system predictive back gestures</string>
<string name="restart_to_apply">Restart to apply</string> <string name="restart_to_apply">Restart to apply</string>
<string name="custom_theme">Custom Theme</string> <string name="custom_theme">Custom Theme</string>
<string name="appearance_setting">外观设置</string> <string name="custom_theme_colors">Custom Colors</string>
<string name="appearance_setting">Appearance Settings</string>
<string name="click_to_select">Click to select</string> <string name="click_to_select">Click to select</string>
<string name="compose_related">Compose Related</string> <string name="compose_related">Compose Related</string>
<string name="use_flexible_top_bar">Use Flexible App Top Bar</string> <string name="use_flexible_top_bar">Use Flexible App Top Bar</string>
@@ -92,25 +93,25 @@
<string name="setting">Settings</string> <string name="setting">Settings</string>
<string name="theme_setting">Theme Settings</string> <string name="theme_setting">Theme Settings</string>
<string name="theme_setting_s">Some settings related to interface or color</string> <string name="theme_setting_s">Some settings related to interface or color</string>
<string name="personalization_setting">深度个性化设置</string> <string name="personalization_setting">Advanced Personalization</string>
<string name="personalization_setting_s">字体等深度个性化设置</string> <string name="personalization_setting_s">Fine-tune fonts and other appearance details</string>
<string name="theme_pack">主题管理</string> <string name="theme_pack">Theme Management</string>
<string name="theme_pack_s">管理、导入导出完整主题包</string> <string name="theme_pack_s">Manage, import, and export complete theme packs</string>
<string name="font_setting">字体设置</string> <string name="font_setting">Font Settings</string>
<string name="font_setting_s">选择应用字体</string> <string name="font_setting_s">Choose the app font</string>
<string name="color_setting">颜色设置</string> <string name="color_setting">Color Settings</string>
<string name="reset_color">重置颜色</string> <string name="reset_color">Reset Colors</string>
<string name="top_bar_color">应用栏颜色</string> <string name="top_bar_color">App Bar Color</string>
<string name="nav_bar_color">导航栏颜色</string> <string name="nav_bar_color">Navigation Bar Color</string>
<string name="font_color">字体颜色</string> <string name="font_color">Font Color</string>
<string name="background_color">背景颜色</string> <string name="background_color">Background Color</string>
<string name="border_setting">边框设置</string> <string name="border_setting">Border Settings</string>
<string name="show_container_border">显示容器边框</string> <string name="show_container_border">Show Container Border</string>
<string name="border_width">边框粗细</string> <string name="border_width">Border Width</string>
<string name="border_style">边框样式</string> <string name="border_style">Border Style</string>
<string name="solid_border">实线</string> <string name="solid_border">Solid</string>
<string name="dashed_border">虚线</string> <string name="dashed_border">Dashed</string>
<string name="dash_gap">虚线间隔</string> <string name="dash_gap">Dash Gap</string>
<string name="other_setting">Other Settings</string> <string name="other_setting">Other Settings</string>
<string name="other_setting_s">Language • Other Settings</string> <string name="other_setting_s">Language • Other Settings</string>
<string name="about">About</string> <string name="about">About</string>
@@ -125,6 +126,16 @@
<string name="origin_format">Origin: %s</string> <string name="origin_format">Origin: %s</string>
<string name="read_dur_progress">Origin: %s</string> <string name="read_dur_progress">Origin: %s</string>
<string name="book_name">Title</string> <string name="book_name">Title</string>
<string name="cover_url">Cover URL</string>
<string name="fixed_book_type">Lock Book Type</string>
<string name="fixed_book_type_summary">Do not overwrite the book type when the book updates</string>
<string name="book_type_label">Book Type</string>
<string name="book_type_text">Text</string>
<string name="book_type_audio">Audio</string>
<string name="book_type_image">Image</string>
<string name="add_tag">Add Tag</string>
<string name="edit_tag">Edit Tag</string>
<string name="tag">Tag</string>
<string name="lasted_show">Latest: %s</string> <string name="lasted_show">Latest: %s</string>
<string name="check_add_bookshelf">Would you like to add %s to your Bookshelf?</string> <string name="check_add_bookshelf">Would you like to add %s to your Bookshelf?</string>
<string name="import_books_count">%s text file(s) in total</string> <string name="import_books_count">%s text file(s) in total</string>
@@ -151,6 +162,8 @@
<string name="pt_auto_download">Auto-download</string> <string name="pt_auto_download">Auto-download</string>
<string name="ps_auto_download">Download the latest chapters automatically when updating books</string> <string name="ps_auto_download">Download the latest chapters automatically when updating books</string>
<string name="backup_restore">Backup and Restore</string> <string name="backup_restore">Backup and Restore</string>
<string name="backup_error">Backup failed</string>
<string name="restore_error">Restore failed</string>
<string name="web_dav_set">WebDAV settings</string> <string name="web_dav_set">WebDAV settings</string>
<string name="web_dav_set_import_old">WebDAV settings • Import legacy data</string> <string name="web_dav_set_import_old">WebDAV settings • Import legacy data</string>
<string name="backup">Backup</string> <string name="backup">Backup</string>
@@ -168,6 +181,11 @@
<string name="on_restore">Restoring</string> <string name="on_restore">Restoring</string>
<string name="restore_success">Restore succeed</string> <string name="restore_success">Restore succeed</string>
<string name="restore_fail">Backup failed</string> <string name="restore_fail">Backup failed</string>
<string name="restore_fail_with_error">Restore failed: %1$s</string>
<string name="restore_from_local">Restore from Local</string>
<string name="restore_from_network">Restore from Network</string>
<string name="webdav_restore_fallback_message">WebDAV error\n%1$s\nRestore from a local backup instead.</string>
<string name="webdav_restore_fail">WebDAV restore failed\n%1$s</string>
<string name="screen_direction">Screen Orientation</string> <string name="screen_direction">Screen Orientation</string>
<string name="screen_sensor">Auto(sensor)</string> <string name="screen_sensor">Auto(sensor)</string>
<string name="screen_landscape">Landscape</string> <string name="screen_landscape">Landscape</string>
@@ -191,6 +209,13 @@
<string name="audio_pause">Pause</string> <string name="audio_pause">Pause</string>
<string name="text_return">Back</string> <string name="text_return">Back</string>
<string name="refresh">Refresh</string> <string name="refresh">Refresh</string>
<string name="pull_to_refresh">Pull to refresh</string>
<string name="release_to_refresh">Release to refresh</string>
<string name="refreshing">Refreshing</string>
<string name="expand">Expand</string>
<string name="collapse">Collapse</string>
<string name="select_color">Select Color</string>
<string name="color_value">Color Value</string>
<string name="start">Start</string> <string name="start">Start</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="pause">Pause</string> <string name="pause">Pause</string>
@@ -643,12 +668,12 @@
<string name="bg_image">Background Images</string> <string name="bg_image">Background Images</string>
<string name="bg_color">Background color</string> <string name="bg_color">Background color</string>
<string name="text_color">Text Color</string> <string name="text_color">Text Color</string>
<string name="header_color">页眉</string> <string name="header_color">Header</string>
<string name="footer_color">页脚</string> <string name="footer_color">Footer</string>
<string name="header_font">设置字体</string> <string name="header_font">Header Font</string>
<string name="footer_font">页脚字体</string> <string name="footer_font">Footer Font</string>
<string name="header_font_size">设置字号</string> <string name="header_font_size">Header Font Size</string>
<string name="footer_font_size">页脚字号</string> <string name="footer_font_size">Footer Font Size</string>
<string name="select_image">Select a picture</string> <string name="select_image">Select a picture</string>
<string name="group_manage">Group Management</string> <string name="group_manage">Group Management</string>
<string name="group_select">Group selection</string> <string name="group_select">Group selection</string>
@@ -671,6 +696,8 @@
<string name="tts">TTS</string> <string name="tts">TTS</string>
<string name="web_dav_pw">WebDAV password</string> <string name="web_dav_pw">WebDAV password</string>
<string name="web_dav_pw_s">Input you WebDAV authorized password</string> <string name="web_dav_pw_s">Input you WebDAV authorized password</string>
<string name="show_password">Show password</string>
<string name="hide_password">Hide password</string>
<string name="web_dav_url">WebDAV Server Address</string> <string name="web_dav_url">WebDAV Server Address</string>
<string name="web_dav_url_s">Input your WebDAV server address</string> <string name="web_dav_url_s">Input your WebDAV server address</string>
<string name="web_dav_account">WebDAV Account</string> <string name="web_dav_account">WebDAV Account</string>
@@ -770,11 +797,11 @@
<string name="contact">Contact</string> <string name="contact">Contact</string>
<string name="license">License</string> <string name="license">License</string>
<string name="other">More Settings</string> <string name="other">More Settings</string>
<string name="official_account">开源阅读</string> <string name="official_account">Open Source Reading</string>
<string name="follow_official_account">Follow WeChat Official Accounts</string> <string name="follow_official_account">Follow WeChat Official Accounts</string>
<string name="wechat">WeChat</string> <string name="wechat">WeChat</string>
<string name="thanks">Supporting me will be appreciated</string> <string name="thanks">Supporting me will be appreciated</string>
<string name="about_official_account">Official Accounts[开源阅读]</string> <string name="about_official_account">Official Account [Open Source Reading]</string>
<string name="source_auto_changing">Changing source</string> <string name="source_auto_changing">Changing source</string>
<string name="click_to_apply">Tap to join</string> <string name="click_to_apply">Tap to join</string>
<string name="middle">Middle</string> <string name="middle">Middle</string>
@@ -818,7 +845,7 @@
<string name="starting_download">Starting download</string> <string name="starting_download">Starting download</string>
<string name="already_in_download">This book is already in Download list</string> <string name="already_in_download">This book is already in Download list</string>
<string name="click_to_open">Click to open</string> <string name="click_to_open">Click to open</string>
<string name="follow_public_account_summary">Follow [开源阅读] to support me by clicking on ads</string> <string name="follow_public_account_summary">Follow [Open Source Reading] to support me by clicking on ads</string>
<string name="weChat_appreciation_code">WeChat Tipping Code</string> <string name="weChat_appreciation_code">WeChat Tipping Code</string>
<string name="alipay">AliPay</string> <string name="alipay">AliPay</string>
<string name="alipay_red_envelope_search_code">AliPay red envelope search code</string> <string name="alipay_red_envelope_search_code">AliPay red envelope search code</string>
@@ -849,7 +876,7 @@
<string name="language">Language</string> <string name="language">Language</string>
<string name="import_rss_source">Import rss source</string> <string name="import_rss_source">Import rss source</string>
<string name="donate_summary">Your donation makes this app better</string> <string name="donate_summary">Your donation makes this app better</string>
<string name="about_summary">Wechat official account [开源阅读软件]</string> <string name="about_summary">WeChat official account [Open Source Reading]</string>
<string name="read_record">Read Record</string> <string name="read_record">Read Record</string>
<string name="del_read_record">Delete Read record</string> <string name="del_read_record">Delete Read record</string>
<string name="read_record_summary">Read record summary</string> <string name="read_record_summary">Read record summary</string>
@@ -976,6 +1003,10 @@
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string> <string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
<string name="upload_url">upload URL</string> <string name="upload_url">upload URL</string>
<string name="download_url_rule">downloadUrlRule(downloadUrls)</string> <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
<string name="clipboard_empty">Clipboard is empty</string>
<string name="clipboard_empty_or_invalid">Clipboard is empty or has an invalid format</string>
<string name="invalid_format">Invalid format</string>
<string name="complete_required_information">Fill in all required information</string>
<string name="export_success">export success</string> <string name="export_success">export success</string>
<string name="path">path</string> <string name="path">path</string>
<string name="direct_link_upload_rule">Direct Link Upload Rules</string> <string name="direct_link_upload_rule">Direct Link Upload Rules</string>
@@ -1042,7 +1073,7 @@
<string name="scope_title">Act on the title</string> <string name="scope_title">Act on the title</string>
<string name="scope_content">Act on the content</string> <string name="scope_content">Act on the content</string>
<string name="join_qq_channel">Join QQ channel</string> <string name="join_qq_channel">Join QQ channel</string>
<string name="qq_channel_summary">点击加入阅读QQ频道</string> <string name="qq_channel_summary">Tap to join the Reading QQ channel</string>
<string name="menu_refresh_dur">Refresh the current chapter</string> <string name="menu_refresh_dur">Refresh the current chapter</string>
<string name="menu_refresh_after">Refresh the chapter after</string> <string name="menu_refresh_after">Refresh the chapter after</string>
<string name="menu_refresh_all">Refresh all chapters</string> <string name="menu_refresh_all">Refresh all chapters</string>
@@ -1057,7 +1088,7 @@
<string name="welcome_style">Launch interface style</string> <string name="welcome_style">Launch interface style</string>
<string name="welcome_style_summary">Startup screen image and whether to display text, etc.</string> <string name="welcome_style_summary">Startup screen image and whether to display text, etc.</string>
<string name="show_welcome_text">Display text</string> <string name="show_welcome_text">Display text</string>
<string name="welcome_text">阅读|享受美好时光</string> <string name="welcome_text">Read | Enjoy the Moment</string>
<string name="custom_welcome">Customize the welcome page</string> <string name="custom_welcome">Customize the welcome page</string>
<string name="custom_welcome_summary">Whether to use a customized welcome page</string> <string name="custom_welcome_summary">Whether to use a customized welcome page</string>
<string name="show_icon">Show icon</string> <string name="show_icon">Show icon</string>
@@ -1071,6 +1102,8 @@
<string name="search_via_url">Search via URL</string> <string name="search_via_url">Search via URL</string>
<string name="cover_rule_edit">Cover Rules</string> <string name="cover_rule_edit">Cover Rules</string>
<string name="cover_rule_summary">Use the cover rule to retrieve the cover when entering the details page</string> <string name="cover_rule_summary">Use the cover rule to retrieve the cover when entering the details page</string>
<string name="cover_rule_fields_required">Search URL and cover rule cannot be empty</string>
<string name="cover_selected_images_count">%1$d images selected</string>
<string name="scroll_to_dur_source">Navigate to the current book source</string> <string name="scroll_to_dur_source">Navigate to the current book source</string>
<string name="sys_tts_config">System tts Settings</string> <string name="sys_tts_config">System tts Settings</string>
<string name="sys_tts_config_summary">Open the system tts settings interface</string> <string name="sys_tts_config_summary">Open the system tts settings interface</string>
@@ -1103,7 +1136,7 @@
<!-- string end --> <!-- string end -->
<string name="error_decode_bitmap">Fail to decode bitmap</string> <string name="error_decode_bitmap">Fail to decode bitmap</string>
<string name="error_image_url_empty">Image URL is empty, check replacement rules</string> <string name="error_image_url_empty">Image URL is empty, check replacement rules</string>
<string name="variable_comment">变量说明(variableComment)</string> <string name="variable_comment">Variable Description (variableComment)</string>
<string name="remote_book">Remote book</string> <string name="remote_book">Remote book</string>
<string name="reading_time_sort">Sort by reading duration</string> <string name="reading_time_sort">Sort by reading duration</string>
<string name="last_read_time_sort">Sort by read time</string> <string name="last_read_time_sort">Sort by read time</string>
@@ -1116,8 +1149,8 @@
<string name="show_add_to_shelf_alert_title">Prompt to Add to Bookshelf When Return</string> <string name="show_add_to_shelf_alert_title">Prompt to Add to Bookshelf When Return</string>
<string name="show_add_to_shelf_alert_summary">Prompt to add to bookshelf when return from reading books that are not on the bookshelf</string> <string name="show_add_to_shelf_alert_summary">Prompt to add to bookshelf when return from reading books that are not on the bookshelf</string>
<string name="review">Review</string> <string name="review">Review</string>
<string name="rule_avatar">段评发布者头像(avatarRule)</string> <string name="rule_avatar">Paragraph Review Author Avatar (avatarRule)</string>
<string name="rule_review_url">段评URL(reviewUrl)</string> <string name="rule_review_url">Paragraph Review URL (reviewUrl)</string>
<string name="rule_review_content">Paragraph review content(contentRule)</string> <string name="rule_review_content">Paragraph review content(contentRule)</string>
<string name="rule_post_time">Release time of paragraph comments(postTimeRule)</string> <string name="rule_post_time">Release time of paragraph comments(postTimeRule)</string>
<string name="rule_review_quote">Paragraph comment replied to URL(reviewQuoteUrl)</string> <string name="rule_review_quote">Paragraph comment replied to URL(reviewQuoteUrl)</string>
@@ -1167,28 +1200,29 @@
<string name="same_title_removed">Remove Duplicate Title</string> <string name="same_title_removed">Remove Duplicate Title</string>
<string name="update_book_fail">Update failed</string> <string name="update_book_fail">Update failed</string>
<string name="notification_permission_rationale">Reading requires sending notifications to show reading control and download progress</string> <string name="notification_permission_rationale">Reading requires sending notifications to show reading control and download progress</string>
<string name="permission_not_required">Permission not required</string>
<string name="webdav_after_local_restore_confirm">The WebDAV book source is newer than the local one. Do you want to restore it?</string> <string name="webdav_after_local_restore_confirm">The WebDAV book source is newer than the local one. Do you want to restore it?</string>
<string name="c_whitelist">白名单(contentWhitelist)</string> <string name="c_whitelist">Whitelist (contentWhitelist)</string>
<string name="c_blacklist">黑名单(contentBlacklist)</string> <string name="c_blacklist">Blacklist (contentBlacklist)</string>
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>
<string name="jump_to_another_app">Jump to another app</string> <string name="jump_to_another_app">Jump to another app</string>
<string name="clear_webview_data">Clear WebView Data</string> <string name="clear_webview_data">Clear WebView Data</string>
<string name="clear_webview_data_summary">Clear all data in WebView</string> <string name="clear_webview_data_summary">Clear all data in WebView</string>
<string name="source_tab_list">List</string> <string name="source_tab_list">List</string>
<string name="source_tab_start">启动</string> <string name="source_tab_start">Startup</string>
<string name="r_startHtml">启动页html文本</string> <string name="r_startHtml">Startup page HTML text</string>
<string name="r_startStyle">启动页style文本</string> <string name="r_startStyle">Startup page style text</string>
<string name="r_startJs">启动页Js(startJs)</string> <string name="r_startJs">Startup page JS (startJs)</string>
<string name="r_preloadJs">预注入Js(preloadJs)</string> <string name="r_preloadJs">Pre-injected JS (preloadJs)</string>
<string name="enable_preload">预加载</string> <string name="enable_preload">Preload</string>
<string name="load_with_web_log">显示WebView日志</string> <string name="load_with_web_log">Show WebView Logs</string>
<string name="cache_first">优先缓存</string> <string name="cache_first">Cache First</string>
<string name="layout_type">布局</string> <string name="layout_type">Layout</string>
<string name="dict_rule">Dictionary Rules</string> <string name="dict_rule">Dictionary Rules</string>
<string name="config_dict_rule">Config dict rule</string> <string name="config_dict_rule">Config dict rule</string>
<string name="create">Create</string> <string name="create">Create</string>
<string name="url_rule">URL规则(urlRule)</string> <string name="url_rule">URL Rule (urlRule)</string>
<string name="show_rule">显示规则(showRule)</string> <string name="show_rule">Display Rule (showRule)</string>
<string name="config_txt_toc_rule">Configure txt catalog rules</string> <string name="config_txt_toc_rule">Configure txt catalog rules</string>
<string name="import_dict_rule">Import dict rule</string> <string name="import_dict_rule">Import dict rule</string>
<string name="keep_group">Keep group</string> <string name="keep_group">Keep group</string>
@@ -1217,7 +1251,7 @@
<string name="respondTime">respondTime: %1$d ms</string> <string name="respondTime">respondTime: %1$d ms</string>
<string name="load_word_count">Load word count</string> <string name="load_word_count">Load word count</string>
<string name="replace_exclude_scope">Exclude range, optional title or source URL</string> <string name="replace_exclude_scope">Exclude range, optional title or source URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">Format Rule (formatJs)</string>
<string name="adjust_pos">Adjustment position</string> <string name="adjust_pos">Adjustment position</string>
<string name="error_scope_input">Please enter the correct range</string> <string name="error_scope_input">Please enter the correct range</string>
<string name="custom_export">Custom Export</string> <string name="custom_export">Custom Export</string>
@@ -1255,6 +1289,23 @@
<string name="key_page_on_long_press">Press and Hold the Key to Turn Page</string> <string name="key_page_on_long_press">Press and Hold the Key to Turn Page</string>
<string name="save_log">Save logs</string> <string name="save_log">Save logs</string>
<string name="create_heap_dump">Create Heap Dump</string> <string name="create_heap_dump">Create Heap Dump</string>
<string name="about_privacy_policy_title">Privacy Policy</string>
<string name="about_license_title">License</string>
<string name="about_disclaimer_title">Disclaimer</string>
<string name="about_installed_version_title">Installed Version</string>
<string name="about_current_version">Installed Version</string>
<string name="about_new_version">Latest Version</string>
<string name="about_update_channel">Release Channel</string>
<string name="about_update_action">Download Update</string>
<string name="about_backup_dir_not_set">Set a backup directory first</string>
<string name="about_log_recording_disabled">Log recording is off. Turn it on in Other Settings first.</string>
<string name="about_saved_to_backup_dir">Saved to backup folder</string>
<string name="about_heap_dump_recording_disabled">Heap dump recording is off. Turn it on in Other Settings first.</string>
<string name="about_heap_dump_creating">Creating heap dump…</string>
<string name="about_heap_dump_not_found">Heap dump file not found</string>
<string name="about_download_info_incomplete">Update download information is incomplete</string>
<string name="about_no_data">No update log available</string>
<string name="about_start_download">Downloading %s</string>
<string name="crash_report">Crash report</string> <string name="crash_report">Crash report</string>
<string name="crash_report_message">The app crashed. Copy the error information below when reporting the issue.</string> <string name="crash_report_message">The app crashed. Copy the error information below when reporting the issue.</string>
<string name="crash_report_empty">No crash information found.</string> <string name="crash_report_empty">No crash information found.</string>
@@ -1283,7 +1334,7 @@
<string name="read_aloud_read_phone_state_permission_rationale">Reading requires phone state access to pause reading aloud during calls</string> <string name="read_aloud_read_phone_state_permission_rationale">Reading requires phone state access to pause reading aloud during calls</string>
<string name="read_aloud_by_media_button_title">Start Read Aloud via Headset Buttons</string> <string name="read_aloud_by_media_button_title">Start Read Aloud via Headset Buttons</string>
<string name="read_aloud_by_media_button_summary">Use headset buttons to start reading aloud</string> <string name="read_aloud_by_media_button_summary">Use headset buttons to start reading aloud</string>
<string name="group_sources_by_domain">按域名分组显示</string> <string name="group_sources_by_domain">Group Sources by Domain</string>
<string name="theme_config">Theme Configuration</string> <string name="theme_config">Theme Configuration</string>
<string name="show_manga_ui">Manga Viewer</string> <string name="show_manga_ui">Manga Viewer</string>
<string name="disable_manga_scale">Disable Manga Zoom</string> <string name="disable_manga_scale">Disable Manga Zoom</string>
@@ -1316,6 +1367,65 @@
<string name="ungroup">Ungrouped</string> <string name="ungroup">Ungrouped</string>
<string name="dynamic_colors">Dynamic Colors</string> <string name="dynamic_colors">Dynamic Colors</string>
<string name="dynamic_colors_summary">Use wallpaper-based theme colors</string> <string name="dynamic_colors_summary">Use wallpaper-based theme colors</string>
<string name="theme_manage_export_success">Theme exported</string>
<string name="theme_manage_export_failed">Could not export theme</string>
<string name="theme_manage_import_success">Theme imported. Some changes require a restart.</string>
<string name="theme_manage_import_failed">Could not import theme</string>
<string name="theme_manage_save_current">Save Current Theme</string>
<string name="theme_manage_save_current_summary">Save the current appearance settings as a theme</string>
<string name="theme_manage_export_current">Export Current Theme</string>
<string name="theme_manage_export_current_summary">Export the current theme settings as a JSON file</string>
<string name="theme_manage_import_config">Import Theme File</string>
<string name="theme_manage_import_config_summary">Import theme settings from a JSON file</string>
<string name="theme_manage_saved_themes">Saved Themes</string>
<string name="theme_manage_save_theme">Save Theme</string>
<string name="theme_manage_save">Save</string>
<string name="theme_manage_name_hint">Enter a theme name</string>
<string name="theme_manage_apply_theme">Apply Theme</string>
<string name="theme_manage_apply">Apply</string>
<string name="theme_manage_apply_message">Apply theme \"%s\"? A restart is required for all changes to take effect.</string>
<string name="theme_manage_delete_theme">Delete Theme</string>
<string name="theme_manage_delete_message">Delete theme \"%s\"? This cannot be undone.</string>
<string name="theme_manage_preview_day">Day</string>
<string name="theme_manage_preview_night">Night</string>
<string name="theme_manage_edit_theme">Edit Theme</string>
<string name="theme_manage_section_basic">Basic Settings</string>
<string name="theme_manage_section_colors">Colors</string>
<string name="theme_manage_use_palette_colors">Generate Colors from Palette</string>
<string name="theme_manage_primary_color">Primary Color</string>
<string name="theme_manage_secondary_color">Secondary Color</string>
<string name="theme_manage_primary_text_color">Primary Text Color</string>
<string name="theme_manage_secondary_text_color">Secondary Text Color</string>
<string name="theme_manage_background_color">Background Color</string>
<string name="theme_manage_label_container_color">Label Container Color</string>
<string name="theme_manage_day_seed_color">Day Seed Color</string>
<string name="theme_manage_night_seed_color">Night Seed Color</string>
<string name="theme_manage_section_layout">Interface Layout</string>
<string name="theme_manage_show_discovery">Show Discovery</string>
<string name="theme_manage_status_bar">Status Bar</string>
<string name="theme_manage_page_turn_animation">Page Turn Animation</string>
<string name="theme_manage_label_visibility">Label Visibility</string>
<string name="theme_manage_section_blur">Blur Effects</string>
<string name="theme_manage_top_bar_blur_radius">Top Bar Blur Radius</string>
<string name="theme_manage_bottom_bar_blur_radius">Bottom Bar Blur Radius</string>
<string name="theme_manage_top_bar_blur_opacity">Top Bar Blur Opacity</string>
<string name="theme_manage_bottom_bar_blur_opacity">Bottom Bar Blur Opacity</string>
<string name="theme_manage_section_opacity">Opacity</string>
<string name="theme_manage_section_container">Container</string>
<string name="theme_config_manage_label_colors">Manage Label Colors</string>
<string name="theme_config_label_color_name">Label %1$d</string>
<string name="theme_config_miuix_experimental_warning">Miuix is currently experimental and does not apply to View-based screens.</string>
<string name="theme_config_bottom_bar_lens_radius">Bottom Bar Blur Strength</string>
<string name="theme_config_bottom_bar_lens_radius_summary">Controls the liquid glass distortion strength</string>
<string name="theme_config_blur_radius_performance_summary">Higher blur radius may reduce performance</string>
<string name="theme_config_divider_width">Divider Width</string>
<string name="theme_config_divider_length">Divider Length</string>
<string name="theme_config_nav_icon_settings">Navigation Bar Icon Settings</string>
<string name="theme_config_nav_icons">Navigation Bar Icons</string>
<string name="theme_config_nav_icons_custom_count">%1$d custom icons set</string>
<string name="theme_config_nav_icons_default">Using default icons</string>
<string name="theme_config_add_nav_icon">Add %1$s icon</string>
<string name="theme_config_no_font_files">No font files</string>
<string name="input_value_range">Enter value (%1$d-%2$d)</string> <string name="input_value_range">Enter value (%1$d-%2$d)</string>
<string name="change_icon_success">Icon Updated</string> <string name="change_icon_success">Icon Updated</string>
<string name="replace_rule_title_setting">Configure Replace Rules</string> <string name="replace_rule_title_setting">Configure Replace Rules</string>
@@ -1363,6 +1473,38 @@
<string name="abi_arm64_v8a">ABI: arm64-v8a</string> <string name="abi_arm64_v8a">ABI: arm64-v8a</string>
<string name="download_link">Download Link</string> <string name="download_link">Download Link</string>
<string name="no_bookmark">No Bookmarks</string> <string name="no_bookmark">No Bookmarks</string>
<string name="bookmark_management">Bookmark Management</string>
<string name="locate_current_reading">Go to Current Reading</string>
<string name="move_to_top">Move to Top</string>
<string name="move_to_bottom">Move to Bottom</string>
<string name="invert_selection">Invert Selection</string>
<string name="select_following">Select Following</string>
<string name="search_chapters">Search chapters…</string>
<string name="use_replace_rule">Use Replacement Rules</string>
<string name="show_word_count">Show Word Count</string>
<string name="local_book_options">Local Book Options</string>
<string name="local_book_toc_rule">Local Book Chapter Rule</string>
<string name="select_toc_rule">Select Chapter Rule</string>
<string name="split_long_chapters">Split Long Chapters</string>
<string name="export_bookmarks_json">Export Bookmarks as JSON</string>
<string name="export_bookmarks_markdown">Export Bookmarks as Markdown</string>
<string name="volume_management">Volume Management</string>
<string name="quick_jump">Quick Jump</string>
<string name="download_selected_count">Download Selected (%1$d)</string>
<string name="toc_rule_update_failed">Failed to update chapter rule: %1$s</string>
<string name="toc_rule_updated">Chapter rule updated</string>
<string name="setting_failed">Setting failed: %1$s</string>
<string name="split_long_chapters_enabled">Long chapter splitting enabled</string>
<string name="split_long_chapters_disabled">Long chapter splitting disabled</string>
<string name="no_bookmarks_to_export">No bookmarks to export</string>
<string name="save_success">Saved</string>
<string name="save_failed_with_error">Save failed: %1$s</string>
<string name="select_chapters">Select chapters</string>
<string name="bookmarks_added_count">%1$d bookmarks added</string>
<string name="start_downloading_chapters">Downloading %1$d chapters</string>
<string name="start_downloading_chapter">Downloading chapter</string>
<string name="all_chapters_cached">All chapters are cached</string>
<string name="start_downloading_remaining_chapters">Downloading remaining %1$d chapters</string>
<string name="read_end">Finished Reading</string> <string name="read_end">Finished Reading</string>
<string name="system_engine">System Engine</string> <string name="system_engine">System Engine</string>
<string name="copy_from">Import from Clipboard</string> <string name="copy_from">Import from Clipboard</string>
@@ -1394,61 +1536,61 @@
<string name="author_read">%1$s • %2$d Not read</string> <string name="author_read">%1$s • %2$d Not read</string>
<string name="menu_alpha_sum">Current %d%%</string> <string name="menu_alpha_sum">Current %d%%</string>
<string name="menu_alpha">Menu Transparency</string> <string name="menu_alpha">Menu Transparency</string>
<string name="change_source_option_title">请选择操作</string> <string name="change_source_option_title">Choose an Action</string>
<string name="replace_current_book">替换本书</string> <string name="replace_current_book">Replace This Book</string>
<string name="add_as_new_book">新增书籍</string> <string name="add_as_new_book">Add as New Book</string>
<string name="quick_add_to_shelf">快速添加到书架</string> <string name="quick_add_to_shelf">Quick Add to Bookshelf</string>
<string name="book_added_to_shelf">书籍已添加到书架</string> <string name="book_added_to_shelf">Book added to bookshelf</string>
<string name="seed_photo_summary">使用图片生成主题颜色</string> <string name="seed_photo_summary">Generate theme colors from an image</string>
<string name="seed_photo_alart">若需使用种子颜色,请取消种子图片选择</string> <string name="seed_photo_alart">To use a seed color, clear the selected seed image first</string>
<string name="click_to_delete">Click to delete or change</string> <string name="click_to_delete">Click to delete or change</string>
<string name="tip_custom_mode">由于主题引擎依赖Monet,对于低于Android12的设备,动态取色与自定义主题不会生效。</string> <string name="tip_custom_mode">The theme engine depends on Monet. Dynamic colors and custom themes do not take effect on devices below Android 12.</string>
<string name="restart_required_message">修改该选项需要重启</string> <string name="restart_required_message">Changing this option requires a restart</string>
<string name="restart_later_message">稍后重启</string> <string name="restart_later_message">Restart Later</string>
<string name="search_select_group">选择书源分组</string> <string name="search_select_group">Select Book Source Group</string>
<string name="day_night_switch">深浅色模式开关</string> <string name="day_night_switch">Light/Dark Mode Switch</string>
<string name="manga_long_click">长按保存图片</string> <string name="manga_long_click">Long press to save image</string>
<string name="read_type_long">长按设置为全局默认模式</string> <string name="read_type_long">Long press to set as the global default mode</string>
<string name="read_slider_mode">滑动条显示样式</string> <string name="read_slider_mode">Slider Display Style</string>
<string name="bookshelf_update_limit">书架更新数量限制</string> <string name="bookshelf_update_limit">Bookshelf Update Limit</string>
<string name="text_shadow_x">X轴距离</string> <string name="text_shadow_x">X Offset</string>
<string name="text_shadow_y">Y轴距离</string> <string name="text_shadow_y">Y Offset</string>
<string name="text_shadow_radius">阴影半径</string> <string name="text_shadow_radius">Shadow Radius</string>
<string name="text_shadow_color">阴影</string> <string name="text_shadow_color">Shadow</string>
<string name="global_read_setting">阅读显示 • 漫画相关</string> <string name="global_read_setting">Reading Display • Manga</string>
<string name="screen_settings">屏幕显示与亮度</string> <string name="screen_settings">Display and Brightness</string>
<string name="refresh_limit_unlimited">Unlimited</string> <string name="refresh_limit_unlimited">Unlimited</string>
<string name="refresh_limit_books">%1$d Books</string> <string name="refresh_limit_books">%1$d Books</string>
<string name="reading_behavior">阅读行为</string> <string name="reading_behavior">Reading Behavior</string>
<string name="page_control">翻页控制</string> <string name="page_control">Page Turn Controls</string>
<string name="redirect_policy">网页重定向策略</string> <string name="redirect_policy">Web Redirect Policy</string>
<string name="redirect_allow_all">允许所有重定向</string> <string name="redirect_allow_all">Allow All Redirects</string>
<string name="redirect_ask_always">总是询问</string> <string name="redirect_ask_always">Always Ask</string>
<string name="redirect_ask_cross_origin">域名不同时询问</string> <string name="redirect_ask_cross_origin">Ask When Domain Changes</string>
<string name="redirect_block_cross_origin">阻止跨域名重定向</string> <string name="redirect_block_cross_origin">Block Cross-Domain Redirects</string>
<string name="redirect_block_all">阻止所有重定向</string> <string name="redirect_block_all">Block All Redirects</string>
<string name="redirect_ask_same_domain_block_cross">域内询问且域外阻止</string> <string name="redirect_ask_same_domain_block_cross">Ask Within Domain, Block Cross-Domain</string>
<string name="show_status">Show Status Bar</string> <string name="show_status">Show Status Bar</string>
<string name="sort_old_to_new">从旧到新</string> <string name="sort_old_to_new">Oldest First</string>
<string name="sort_new_to_old">从新到旧</string> <string name="sort_new_to_old">Newest First</string>
<string name="sort_name_asc">名称升序</string> <string name="sort_name_asc">Name Ascending</string>
<string name="sort_name_desc">名称降序</string> <string name="sort_name_desc">Name Descending</string>
<string name="text_dottedline">下划虚线</string> <string name="text_dottedline">Dotted Underline</string>
<string name="jump_to_volume">跳转到卷</string> <string name="jump_to_volume">Jump to Volume</string>
<string name="cancel_select">取消选择</string> <string name="cancel_select">Cancel Selection</string>
<string name="coll_volume">折叠所有卷</string> <string name="coll_volume">Collapse All Volumes</string>
<string name="expand_volume">展开所有卷</string> <string name="expand_volume">Expand All Volumes</string>
<string name="custom">自定义</string> <string name="custom">Custom</string>
<string name="global_setting">全局设置</string> <string name="global_setting">Global Settings</string>
<string name="text_typeface">字体样式</string> <string name="text_typeface">Font Style</string>
<string name="shadow_typeface">阴影样式</string> <string name="shadow_typeface">Shadow Style</string>
<string name="long_click_to_custom">Long Tap to Customize</string> <string name="long_click_to_custom">Long Tap to Customize</string>
<string name="disable_manga_scroll_animation">禁用翻页动画</string> <string name="disable_manga_scroll_animation">Disable Page Turn Animation</string>
<string name="manga_cross_fade">禁用加载淡入动画</string> <string name="manga_cross_fade">Disable Loading Fade-In Animation</string>
<string name="text_shadow_set">阴影设置</string> <string name="text_shadow_set">Shadow Settings</string>
<string name="paragraph_typeface">段落样式</string> <string name="paragraph_typeface">Paragraph Style</string>
<string name="same_name_book">已有相同书籍</string> <string name="same_name_book">A book with the same name already exists</string>
<string name="underline_color">Underline Color</string> <string name="underline_color">Underline Color</string>
<string name="dotted_line_black">Dash Length</string> <string name="dotted_line_black">Dash Length</string>
<string name="dotted_line_while">Gap Length</string> <string name="dotted_line_while">Gap Length</string>
@@ -1457,7 +1599,7 @@
<string name="sure_cache_book">Cache the Book?</string> <string name="sure_cache_book">Cache the Book?</string>
<string name="auto_check_new_backup_t">Auto-Check for New Backups</string> <string name="auto_check_new_backup_t">Auto-Check for New Backups</string>
<string name="auto_check_new_backup_s">Check for new backups when opening the app, if new backups are found, prompt the user to update.</string> <string name="auto_check_new_backup_s">Check for new backups when opening the app, if new backups are found, prompt the user to update.</string>
<string name="replace_purify_badge">计数替换净化</string> <string name="replace_purify_badge">Count Replacements and Purify Rules</string>
<string name="page_anim_fade">Fade</string> <string name="page_anim_fade">Fade</string>
<string name="show_bookshelf_tab_menu">Show More Bookshelf Tab</string> <string name="show_bookshelf_tab_menu">Show More Bookshelf Tab</string>
<string name="show_unread_new">Show New Chapter Flag</string> <string name="show_unread_new">Show New Chapter Flag</string>
@@ -1480,32 +1622,32 @@
<string name="select_operation">Select an operation</string> <string name="select_operation">Select an operation</string>
<string name="text_accent_color">Accent Color</string> <string name="text_accent_color">Accent Color</string>
<string name="read_color">Color Style</string> <string name="read_color">Color Style</string>
<string name="reverse_volume_key_page">反转音量键翻页方向</string> <string name="reverse_volume_key_page">Reverse Volume-Key Page Turn Direction</string>
<string name="palette_style">调色板样式</string> <string name="palette_style">Palette Style</string>
<string name="material_version">颜色规格</string> <string name="material_version">Color Specification</string>
<string name="tip_compose_set">这里的选项仅可以控制使用Compose重写的界面。对于禁用的选项,会在日后实现时开放,现在仅作占位。</string> <string name="tip_compose_set">These options only affect screens rewritten in Compose. Disabled options are placeholders and will be enabled after implementation.</string>
<string name="is_blur_enable">Enable Element Blur</string> <string name="is_blur_enable">Enable Element Blur</string>
<string name="use_underline">总是使用下划线表示强调文本</string> <string name="use_underline">Always Use Underline for Emphasized Text</string>
<string name="adapt_special_style">适配特殊样式</string> <string name="adapt_special_style">Adapt Special Styles</string>
<string name="compose_engine">Theme Style</string> <string name="compose_engine">Theme Style</string>
<string name="container_opacity">Container Opacity</string> <string name="container_opacity">Container Opacity</string>
<string name="container_opacity_summary">%d</string> <string name="container_opacity_summary">%d</string>
<string name="top_bar_opacity">顶栏不透明度</string> <string name="top_bar_opacity">Top Bar Opacity</string>
<string name="top_bar_opacity_summary">%d</string> <string name="top_bar_opacity_summary">%d</string>
<string name="bottom_bar_opacity">底栏不透明度</string> <string name="bottom_bar_opacity">Bottom Bar Opacity</string>
<string name="bottom_bar_opacity_summary">%d</string> <string name="bottom_bar_opacity_summary">%d</string>
<string name="web_service_auto_start">启动应用时打开Web服务</string> <string name="web_service_auto_start">Start Web Service When App Opens</string>
<string name="read_aloud_preload">听书预加载数量</string> <string name="read_aloud_preload">Read-Aloud Preload Count</string>
<string name="read_aloud_preload_summary">%d</string> <string name="read_aloud_preload_summary">%d</string>
<string name="audio_cache_clean_time">音频缓存保留时间(分钟)</string> <string name="audio_cache_clean_time">Audio Cache Retention Time (minutes)</string>
<string name="audio_cache_clean_time_summary">当前%d, 输入 0 代表退出即刻清理。</string> <string name="audio_cache_clean_time_summary">Current: %d. Enter 0 to clear immediately on exit.</string>
<string name="show_swipe_animation">启用滑动时动画</string> <string name="show_swipe_animation">Enable Animation While Swiping</string>
<string name="click_image_way">点击图片行为</string> <string name="click_image_way">Image Tap Action</string>
<string name="show_web_log">输出日志</string> <string name="show_web_log">Output Logs</string>
<string name="is_blur_progressive_enable">启用渐变模糊</string> <string name="is_blur_progressive_enable">Enable Progressive Blur</string>
<string name="enable_select_vibrator">选择文本时震动</string> <string name="enable_select_vibrator">Vibrate When Selecting Text</string>
<string name="re_sync_webdav">重新同步远程书籍</string> <string name="re_sync_webdav">Resync Remote Books</string>
<string name="underline_padding">离字间距</string> <string name="underline_padding">Underline Gap</string>
<!-- Missing strings --> <!-- Missing strings -->
<string name="cover_info_orientation">Cover orientation</string> <string name="cover_info_orientation">Cover orientation</string>