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