Bladeren bron

Migrate to multiplatform string resources (#10147)

* Migrate to multiplatform string resources

* Move plurals translations into separate files

* Fix lint check on generated files
arkon 1 jaar geleden
bovenliggende
commit
46e734fc8e
100 gewijzigde bestanden met toevoegingen van 1138 en 1144 verwijderingen
  1. 7 7
      app/src/main/java/eu/kanade/domain/base/BasePreferences.kt
  2. 14 13
      app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt
  3. 7 6
      app/src/main/java/eu/kanade/domain/ui/model/TabletUiMode.kt
  4. 9 8
      app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
  5. 20 20
      app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt
  6. 4 4
      app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt
  7. 29 29
      app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt
  8. 2 2
      app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt
  9. 9 9
      app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt
  10. 4 4
      app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt
  11. 9 9
      app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
  12. 6 8
      app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceDialogs.kt
  13. 9 9
      app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt
  14. 3 3
      app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchCardRow.kt
  15. 3 3
      app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchResultItems.kt
  16. 5 5
      app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt
  17. 5 4
      app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt
  18. 5 5
      app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
  19. 31 31
      app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt
  20. 3 3
      app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt
  21. 4 4
      app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt
  22. 9 7
      app/src/main/java/eu/kanade/presentation/components/AppBar.kt
  23. 8 8
      app/src/main/java/eu/kanade/presentation/components/Banners.kt
  24. 8 8
      app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt
  25. 4 4
      app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt
  26. 5 6
      app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt
  27. 3 3
      app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt
  28. 6 6
      app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt
  29. 6 6
      app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt
  30. 7 7
      app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt
  31. 11 11
      app/src/main/java/eu/kanade/presentation/history/components/HistoryDialogs.kt
  32. 5 5
      app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt
  33. 11 10
      app/src/main/java/eu/kanade/presentation/library/DeleteLibraryMangaDialog.kt
  34. 39 39
      app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
  35. 3 3
      app/src/main/java/eu/kanade/presentation/library/components/CommonMangaItem.kt
  36. 3 3
      app/src/main/java/eu/kanade/presentation/library/components/GlobalSearchItem.kt
  37. 5 5
      app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt
  38. 8 8
      app/src/main/java/eu/kanade/presentation/library/components/LibraryToolbar.kt
  39. 24 24
      app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt
  40. 7 7
      app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt
  41. 8 8
      app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
  42. 7 6
      app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt
  43. 6 6
      app/src/main/java/eu/kanade/presentation/manga/components/ChapterHeader.kt
  44. 14 13
      app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt
  45. 5 5
      app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt
  46. 8 8
      app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt
  47. 10 10
      app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt
  48. 26 25
      app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt
  49. 10 10
      app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt
  50. 3 3
      app/src/main/java/eu/kanade/presentation/manga/components/MissingChapterCountListItem.kt
  51. 8 8
      app/src/main/java/eu/kanade/presentation/manga/components/ScanlatorFilterDialog.kt
  52. 20 21
      app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt
  53. 6 6
      app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt
  54. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt
  55. 4 4
      app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt
  56. 9 9
      app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt
  57. 2 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SearchableSettings.kt
  58. 52 54
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt
  59. 25 25
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt
  60. 11 12
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt
  61. 39 40
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt
  62. 33 35
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt
  63. 47 49
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt
  64. 29 31
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt
  65. 75 77
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt
  66. 5 5
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt
  67. 16 17
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
  68. 27 29
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt
  69. 12 12
      app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt
  70. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLibraryLicenseScreen.kt
  71. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt
  72. 12 12
      app/src/main/java/eu/kanade/presentation/more/settings/screen/advanced/ClearDatabaseScreen.kt
  73. 15 15
      app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt
  74. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt
  75. 2 1
      app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt
  76. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt
  77. 5 5
      app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt
  78. 4 4
      app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt
  79. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt
  80. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt
  81. 4 4
      app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt
  82. 3 3
      app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt
  83. 8 8
      app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt
  84. 20 20
      app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt
  85. 4 4
      app/src/main/java/eu/kanade/presentation/more/stats/components/StatsSection.kt
  86. 11 11
      app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt
  87. 6 5
      app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt
  88. 8 8
      app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt
  89. 6 5
      app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt
  90. 6 5
      app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt
  91. 7 7
      app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt
  92. 6 6
      app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt
  93. 4 4
      app/src/main/java/eu/kanade/presentation/reader/components/ModeSelectionDialog.kt
  94. 19 19
      app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt
  95. 16 16
      app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt
  96. 5 5
      app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt
  97. 29 29
      app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt
  98. 12 12
      app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt
  99. 19 18
      app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt
  100. 9 10
      app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt

+ 7 - 7
app/src/main/java/eu/kanade/domain/base/BasePreferences.kt

@@ -1,12 +1,12 @@
 package eu.kanade.domain.base
 
 import android.content.Context
-import androidx.annotation.StringRes
-import eu.kanade.tachiyomi.R
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.tachiyomi.util.system.isPreviewBuildType
 import eu.kanade.tachiyomi.util.system.isReleaseBuildType
 import tachiyomi.core.preference.Preference
 import tachiyomi.core.preference.PreferenceStore
+import tachiyomi.i18n.MR
 
 class BasePreferences(
     val context: Context,
@@ -24,10 +24,10 @@ class BasePreferences(
 
     fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
 
-    enum class ExtensionInstaller(@StringRes val titleResId: Int) {
-        LEGACY(R.string.ext_installer_legacy),
-        PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
-        SHIZUKU(R.string.ext_installer_shizuku),
-        PRIVATE(R.string.ext_installer_private),
+    enum class ExtensionInstaller(val titleRes: StringResource) {
+        LEGACY(MR.strings.ext_installer_legacy),
+        PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),
+        SHIZUKU(MR.strings.ext_installer_shizuku),
+        PRIVATE(MR.strings.ext_installer_private),
     }
 }

+ 14 - 13
app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt

@@ -1,19 +1,20 @@
 package eu.kanade.domain.ui.model
 
-import eu.kanade.tachiyomi.R
+import dev.icerock.moko.resources.StringResource
+import tachiyomi.i18n.MR
 
-enum class AppTheme(val titleResId: Int?) {
-    DEFAULT(R.string.label_default),
-    MONET(R.string.theme_monet),
-    GREEN_APPLE(R.string.theme_greenapple),
-    LAVENDER(R.string.theme_lavender),
-    MIDNIGHT_DUSK(R.string.theme_midnightdusk),
-    STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
-    TAKO(R.string.theme_tako),
-    TEALTURQUOISE(R.string.theme_tealturquoise),
-    TIDAL_WAVE(R.string.theme_tidalwave),
-    YINYANG(R.string.theme_yinyang),
-    YOTSUBA(R.string.theme_yotsuba),
+enum class AppTheme(val titleRes: StringResource?) {
+    DEFAULT(MR.strings.label_default),
+    MONET(MR.strings.theme_monet),
+    GREEN_APPLE(MR.strings.theme_greenapple),
+    LAVENDER(MR.strings.theme_lavender),
+    MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
+    STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
+    TAKO(MR.strings.theme_tako),
+    TEALTURQUOISE(MR.strings.theme_tealturquoise),
+    TIDAL_WAVE(MR.strings.theme_tidalwave),
+    YINYANG(MR.strings.theme_yinyang),
+    YOTSUBA(MR.strings.theme_yotsuba),
 
     // Deprecated
     DARK_BLUE(null),

+ 7 - 6
app/src/main/java/eu/kanade/domain/ui/model/TabletUiMode.kt

@@ -1,10 +1,11 @@
 package eu.kanade.domain.ui.model
 
-import eu.kanade.tachiyomi.R
+import dev.icerock.moko.resources.StringResource
+import tachiyomi.i18n.MR
 
-enum class TabletUiMode(val titleResId: Int) {
-    AUTOMATIC(R.string.automatic_background),
-    ALWAYS(R.string.lock_always),
-    LANDSCAPE(R.string.landscape),
-    NEVER(R.string.lock_never),
+enum class TabletUiMode(val titleRes: StringResource) {
+    AUTOMATIC(MR.strings.automatic_background),
+    ALWAYS(MR.strings.lock_always),
+    LANDSCAPE(MR.strings.landscape),
+    NEVER(MR.strings.lock_never),
 }

+ 9 - 8
app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt

@@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.paging.LoadState
 import androidx.paging.compose.LazyPagingItems
 import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
@@ -22,14 +21,16 @@ import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
 import eu.kanade.presentation.browse.components.BrowseSourceList
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.util.formattedMessage
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.source.Source
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.flow.StateFlow
+import tachiyomi.core.i18n.localize
 import tachiyomi.domain.library.model.LibraryDisplayMode
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.source.model.StubSource
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.EmptyScreenAction
 import tachiyomi.presentation.core.screens.LoadingScreen
@@ -62,7 +63,7 @@ fun BrowseSourceContent(
         if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
             val result = snackbarHostState.showSnackbar(
                 message = getErrorMessage(errorState),
-                actionLabel = context.getString(R.string.action_retry),
+                actionLabel = context.localize(MR.strings.action_retry),
                 duration = SnackbarDuration.Indefinite,
             )
             when (result) {
@@ -79,7 +80,7 @@ fun BrowseSourceContent(
             actions = if (source is LocalSource) {
                 persistentListOf(
                     EmptyScreenAction(
-                        stringResId = R.string.local_source_help_guide,
+                        stringRes = MR.strings.local_source_help_guide,
                         icon = Icons.AutoMirrored.Outlined.HelpOutline,
                         onClick = onLocalSourceHelpClick,
                     ),
@@ -87,17 +88,17 @@ fun BrowseSourceContent(
             } else {
                 persistentListOf(
                     EmptyScreenAction(
-                        stringResId = R.string.action_retry,
+                        stringRes = MR.strings.action_retry,
                         icon = Icons.Outlined.Refresh,
                         onClick = mangaList::refresh,
                     ),
                     EmptyScreenAction(
-                        stringResId = R.string.action_open_in_web_view,
+                        stringRes = MR.strings.action_open_in_web_view,
                         icon = Icons.Outlined.Public,
                         onClick = onWebViewClick,
                     ),
                     EmptyScreenAction(
-                        stringResId = R.string.label_help,
+                        stringRes = MR.strings.label_help,
                         icon = Icons.AutoMirrored.Outlined.HelpOutline,
                         onClick = onHelpClick,
                     ),
@@ -160,7 +161,7 @@ internal fun MissingSourceScreen(
         },
     ) { paddingValues ->
         EmptyScreen(
-            message = stringResource(R.string.source_not_installed, source.toString()),
+            message = localize(MR.strings.source_not_installed, source.toString()),
             modifier = Modifier.padding(paddingValues),
         )
     }

+ 20 - 20
app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt

@@ -38,7 +38,6 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
@@ -50,15 +49,16 @@ import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.components.WarningBanner
 import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
 import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.source.ConfigurableSource
 import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
 import eu.kanade.tachiyomi.util.system.LocaleHelper
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 
 @Composable
@@ -77,7 +77,7 @@ fun ExtensionDetailsScreen(
     Scaffold(
         topBar = { scrollBehavior ->
             AppBar(
-                title = stringResource(R.string.label_extension_info),
+                title = localize(MR.strings.label_extension_info),
                 navigateUp = navigateUp,
                 actions = {
                     AppBarActions(
@@ -86,14 +86,14 @@ fun ExtensionDetailsScreen(
                                 if (state.extension?.isUnofficial == false) {
                                     add(
                                         AppBar.Action(
-                                            title = stringResource(R.string.whats_new),
+                                            title = localize(MR.strings.whats_new),
                                             icon = Icons.Outlined.History,
                                             onClick = onClickWhatsNew,
                                         ),
                                     )
                                     add(
                                         AppBar.Action(
-                                            title = stringResource(R.string.action_faq_and_guides),
+                                            title = localize(MR.strings.action_faq_and_guides),
                                             icon = Icons.AutoMirrored.Outlined.HelpOutline,
                                             onClick = onClickReadme,
                                         ),
@@ -102,15 +102,15 @@ fun ExtensionDetailsScreen(
                                 addAll(
                                     listOf(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_enable_all),
+                                            title = localize(MR.strings.action_enable_all),
                                             onClick = onClickEnableAll,
                                         ),
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_disable_all),
+                                            title = localize(MR.strings.action_disable_all),
                                             onClick = onClickDisableAll,
                                         ),
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.pref_clear_cookies),
+                                            title = localize(MR.strings.pref_clear_cookies),
                                             onClick = onClickClearCookies,
                                         ),
                                     ),
@@ -125,7 +125,7 @@ fun ExtensionDetailsScreen(
     ) { paddingValues ->
         if (state.extension == null) {
             EmptyScreen(
-                textResource = R.string.empty_screen,
+                stringRes = MR.strings.empty_screen,
                 modifier = Modifier.padding(paddingValues),
             )
             return@Scaffold
@@ -160,11 +160,11 @@ private fun ExtensionDetails(
         when {
             extension.isUnofficial ->
                 item {
-                    WarningBanner(R.string.unofficial_extension_message)
+                    WarningBanner(MR.strings.unofficial_extension_message)
                 }
             extension.isObsolete ->
                 item {
-                    WarningBanner(R.string.obsolete_extension_message)
+                    WarningBanner(MR.strings.obsolete_extension_message)
                 }
         }
 
@@ -260,7 +260,7 @@ private fun DetailsHeader(
             InfoText(
                 modifier = Modifier.weight(1f),
                 primaryText = extension.versionName,
-                secondaryText = stringResource(R.string.ext_info_version),
+                secondaryText = localize(MR.strings.ext_info_version),
             )
 
             InfoDivider()
@@ -268,7 +268,7 @@ private fun DetailsHeader(
             InfoText(
                 modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
                 primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
-                secondaryText = stringResource(R.string.ext_info_language),
+                secondaryText = localize(MR.strings.ext_info_language),
             )
 
             if (extension.isNsfw) {
@@ -276,12 +276,12 @@ private fun DetailsHeader(
 
                 InfoText(
                     modifier = Modifier.weight(1f),
-                    primaryText = stringResource(R.string.ext_nsfw_short),
+                    primaryText = localize(MR.strings.ext_nsfw_short),
                     primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
                         color = MaterialTheme.colorScheme.error,
                         fontWeight = FontWeight.Medium,
                     ),
-                    secondaryText = stringResource(R.string.ext_info_age_rating),
+                    secondaryText = localize(MR.strings.ext_info_age_rating),
                     onClick = onClickAgeRating,
                 )
             }
@@ -300,7 +300,7 @@ private fun DetailsHeader(
                 modifier = Modifier.weight(1f),
                 onClick = onClickUninstall,
             ) {
-                Text(stringResource(R.string.ext_uninstall))
+                Text(localize(MR.strings.ext_uninstall))
             }
 
             if (onClickAppInfo != null) {
@@ -309,7 +309,7 @@ private fun DetailsHeader(
                     onClick = onClickAppInfo,
                 ) {
                     Text(
-                        text = stringResource(R.string.ext_app_info),
+                        text = localize(MR.strings.ext_app_info),
                         color = MaterialTheme.colorScheme.onPrimary,
                     )
                 }
@@ -387,7 +387,7 @@ private fun SourceSwitchPreference(
                     IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
                         Icon(
                             imageVector = Icons.Outlined.Settings,
-                            contentDescription = stringResource(R.string.label_settings),
+                            contentDescription = localize(MR.strings.label_settings),
                             tint = MaterialTheme.colorScheme.onSurface,
                         )
                     }
@@ -410,11 +410,11 @@ private fun NsfwWarningDialog(
 ) {
     AlertDialog(
         text = {
-            Text(text = stringResource(R.string.ext_nsfw_warning))
+            Text(text = localize(MR.strings.ext_nsfw_warning))
         },
         confirmButton = {
             TextButton(onClick = onClickConfirm) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         onDismissRequest = onClickConfirm,

+ 4 - 4
app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt

@@ -7,13 +7,13 @@ import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
 import eu.kanade.tachiyomi.util.system.LocaleHelper
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 
 @Composable
@@ -25,7 +25,7 @@ fun ExtensionFilterScreen(
     Scaffold(
         topBar = { scrollBehavior ->
             AppBar(
-                title = stringResource(R.string.label_extensions),
+                title = localize(MR.strings.label_extensions),
                 navigateUp = navigateUp,
                 scrollBehavior = scrollBehavior,
             )
@@ -33,7 +33,7 @@ fun ExtensionFilterScreen(
     ) { contentPadding ->
         if (state.isEmpty) {
             EmptyScreen(
-                textResource = R.string.empty_screen,
+                stringRes = MR.strings.empty_screen,
                 modifier = Modifier.padding(contentPadding),
             )
             return@Scaffold

+ 29 - 29
app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.browse
 
-import androidx.annotation.StringRes
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.Arrangement
@@ -33,22 +32,23 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.browse.components.BaseBrowseItem
 import eu.kanade.presentation.browse.components.ExtensionIcon
 import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
 import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
 import eu.kanade.tachiyomi.util.system.LocaleHelper
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.FastScrollLazyColumn
 import tachiyomi.presentation.core.components.material.PullRefresh
 import tachiyomi.presentation.core.components.material.padding
 import tachiyomi.presentation.core.components.material.topSmallPaddingValues
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import tachiyomi.presentation.core.theme.header
@@ -79,12 +79,12 @@ fun ExtensionScreen(
             state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
             state.isEmpty -> {
                 val msg = if (!searchQuery.isNullOrEmpty()) {
-                    R.string.no_results_found
+                    MR.strings.no_results_found
                 } else {
-                    R.string.empty_screen
+                    MR.strings.empty_screen
                 }
                 EmptyScreen(
-                    textResource = msg,
+                    stringRes = msg,
                     modifier = Modifier.padding(contentPadding),
                 )
             }
@@ -132,11 +132,11 @@ private fun ExtensionContent(
                 when (header) {
                     is ExtensionUiModel.Header.Resource -> {
                         val action: @Composable RowScope.() -> Unit =
-                            if (header.textRes == R.string.ext_updates_pending) {
+                            if (header.textRes == MR.strings.ext_updates_pending) {
                                 {
                                     Button(onClick = { onClickUpdateAll() }) {
                                         Text(
-                                            text = stringResource(R.string.ext_update_all),
+                                            text = localize(MR.strings.ext_update_all),
                                             style = LocalTextStyle.current.copy(
                                                 color = MaterialTheme.colorScheme.onPrimary,
                                             ),
@@ -304,15 +304,15 @@ private fun ExtensionItemContent(
                 }
 
                 val warning = when {
-                    extension is Extension.Untrusted -> R.string.ext_untrusted
-                    extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
-                    extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
-                    extension.isNsfw -> R.string.ext_nsfw_short
+                    extension is Extension.Untrusted -> MR.strings.ext_untrusted
+                    extension is Extension.Installed && extension.isUnofficial -> MR.strings.ext_unofficial
+                    extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
+                    extension.isNsfw -> MR.strings.ext_nsfw_short
                     else -> null
                 }
                 if (warning != null) {
                     Text(
-                        text = stringResource(warning).uppercase(),
+                        text = localize(warning).uppercase(),
                         color = MaterialTheme.colorScheme.error,
                         maxLines = 1,
                         overflow = TextOverflow.Ellipsis,
@@ -323,9 +323,9 @@ private fun ExtensionItemContent(
                     DotSeparatorNoSpaceText()
                     Text(
                         text = when (installStep) {
-                            InstallStep.Pending -> stringResource(R.string.ext_pending)
-                            InstallStep.Downloading -> stringResource(R.string.ext_downloading)
-                            InstallStep.Installing -> stringResource(R.string.ext_installing)
+                            InstallStep.Pending -> localize(MR.strings.ext_pending)
+                            InstallStep.Downloading -> localize(MR.strings.ext_downloading)
+                            InstallStep.Installing -> localize(MR.strings.ext_installing)
                             else -> error("Must not show non-install process text")
                         },
                     )
@@ -351,19 +351,19 @@ private fun ExtensionItemActions(
             ) {
                 Text(
                     text = when (installStep) {
-                        InstallStep.Installed -> stringResource(R.string.ext_installed)
-                        InstallStep.Error -> stringResource(R.string.action_retry)
+                        InstallStep.Installed -> localize(MR.strings.ext_installed)
+                        InstallStep.Error -> localize(MR.strings.action_retry)
                         InstallStep.Idle -> {
                             when (extension) {
                                 is Extension.Installed -> {
                                     if (extension.hasUpdate) {
-                                        stringResource(R.string.ext_update)
+                                        localize(MR.strings.ext_update)
                                     } else {
-                                        stringResource(R.string.action_settings)
+                                        localize(MR.strings.action_settings)
                                     }
                                 }
-                                is Extension.Untrusted -> stringResource(R.string.ext_trust)
-                                is Extension.Available -> stringResource(R.string.ext_install)
+                                is Extension.Untrusted -> localize(MR.strings.ext_trust)
+                                is Extension.Available -> localize(MR.strings.ext_install)
                             }
                         }
                         else -> error("Must not show install process text")
@@ -374,7 +374,7 @@ private fun ExtensionItemActions(
             IconButton(onClick = { onClickItemCancel(extension) }) {
                 Icon(
                     imageVector = Icons.Outlined.Close,
-                    contentDescription = stringResource(R.string.action_cancel),
+                    contentDescription = localize(MR.strings.action_cancel),
                 )
             }
         }
@@ -383,12 +383,12 @@ private fun ExtensionItemActions(
 
 @Composable
 private fun ExtensionHeader(
-    @StringRes textRes: Int,
+    textRes: StringResource,
     modifier: Modifier = Modifier,
     action: @Composable RowScope.() -> Unit = {},
 ) {
     ExtensionHeader(
-        text = stringResource(textRes),
+        text = localize(textRes),
         modifier = modifier,
         action = action,
     )
@@ -423,19 +423,19 @@ private fun ExtensionTrustDialog(
 ) {
     AlertDialog(
         title = {
-            Text(text = stringResource(R.string.untrusted_extension))
+            Text(text = localize(MR.strings.untrusted_extension))
         },
         text = {
-            Text(text = stringResource(R.string.untrusted_extension_message))
+            Text(text = localize(MR.strings.untrusted_extension_message))
         },
         confirmButton = {
             TextButton(onClick = onClickConfirm) {
-                Text(text = stringResource(R.string.ext_trust))
+                Text(text = localize(MR.strings.ext_trust))
             }
         },
         dismissButton = {
             TextButton(onClick = onClickDismiss) {
-                Text(text = stringResource(R.string.ext_uninstall))
+                Text(text = localize(MR.strings.ext_uninstall))
             }
         },
         onDismissRequest = onDismissRequest,

+ 2 - 2
app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt

@@ -7,9 +7,9 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.manga.components.BaseMangaListItem
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.FastScrollLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.screens.EmptyScreen
@@ -33,7 +33,7 @@ fun MigrateMangaScreen(
     ) { contentPadding ->
         if (state.isEmpty) {
             EmptyScreen(
-                textResource = R.string.empty_screen,
+                stringRes = MR.strings.empty_screen,
                 modifier = Modifier.padding(contentPadding),
             )
             return@Scaffold

+ 9 - 9
app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt

@@ -20,21 +20,21 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import eu.kanade.domain.source.interactor.SetMigrateSorting
 import eu.kanade.presentation.browse.components.BaseSourceItem
 import eu.kanade.presentation.browse.components.SourceIcon
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import tachiyomi.domain.source.model.Source
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.Badge
 import tachiyomi.presentation.core.components.BadgeGroup
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
 import tachiyomi.presentation.core.components.material.padding
 import tachiyomi.presentation.core.components.material.topSmallPaddingValues
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import tachiyomi.presentation.core.theme.header
@@ -53,7 +53,7 @@ fun MigrateSourceScreen(
     when {
         state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
         state.isEmpty -> EmptyScreen(
-            textResource = R.string.information_empty_library,
+            stringRes = MR.strings.information_empty_library,
             modifier = Modifier.padding(contentPadding),
         )
         else ->
@@ -95,7 +95,7 @@ private fun MigrateSourceList(
                 verticalAlignment = Alignment.CenterVertically,
             ) {
                 Text(
-                    text = stringResource(R.string.migration_selection_prompt),
+                    text = localize(MR.strings.migration_selection_prompt),
                     modifier = Modifier.weight(1f),
                     style = MaterialTheme.typography.header,
                 )
@@ -104,11 +104,11 @@ private fun MigrateSourceList(
                     when (sortingMode) {
                         SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
                             Icons.Outlined.SortByAlpha,
-                            contentDescription = stringResource(R.string.action_sort_alpha),
+                            contentDescription = localize(MR.strings.action_sort_alpha),
                         )
                         SetMigrateSorting.Mode.TOTAL -> Icon(
                             Icons.Outlined.Numbers,
-                            contentDescription = stringResource(R.string.action_sort_count),
+                            contentDescription = localize(MR.strings.action_sort_count),
                         )
                     }
                 }
@@ -116,11 +116,11 @@ private fun MigrateSourceList(
                     when (sortingDirection) {
                         SetMigrateSorting.Direction.ASCENDING -> Icon(
                             Icons.Outlined.ArrowUpward,
-                            contentDescription = stringResource(R.string.action_asc),
+                            contentDescription = localize(MR.strings.action_asc),
                         )
                         SetMigrateSorting.Direction.DESCENDING -> Icon(
                             Icons.Outlined.ArrowDownward,
-                            contentDescription = stringResource(R.string.action_desc),
+                            contentDescription = localize(MR.strings.action_desc),
                         )
                     }
                 }
@@ -189,7 +189,7 @@ private fun MigrateSourceItem(
                     if (source.isStub) {
                         Text(
                             modifier = Modifier.secondaryItemAlpha(),
-                            text = stringResource(R.string.not_installed),
+                            text = localize(MR.strings.not_installed),
                             maxLines = 1,
                             overflow = TextOverflow.Ellipsis,
                             style = MaterialTheme.typography.bodySmall,

+ 4 - 4
app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt

@@ -7,16 +7,16 @@ import androidx.compose.material3.Checkbox
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.browse.components.BaseSourceItem
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
 import eu.kanade.tachiyomi.util.system.LocaleHelper
 import tachiyomi.domain.source.model.Source
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.FastScrollLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 
 @Composable
@@ -29,7 +29,7 @@ fun SourcesFilterScreen(
     Scaffold(
         topBar = { scrollBehavior ->
             AppBar(
-                title = stringResource(R.string.label_sources),
+                title = localize(MR.strings.label_sources),
                 navigateUp = navigateUp,
                 scrollBehavior = scrollBehavior,
             )
@@ -37,7 +37,7 @@ fun SourcesFilterScreen(
     ) { contentPadding ->
         if (state.isEmpty) {
             EmptyScreen(
-                textResource = R.string.source_filter_empty_screen,
+                stringRes = MR.strings.source_filter_empty_screen,
                 modifier = Modifier.padding(contentPadding),
             )
             return@Scaffold

+ 9 - 9
app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt

@@ -19,19 +19,19 @@ import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.browse.components.BaseSourceItem
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
 import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
 import eu.kanade.tachiyomi.util.system.LocaleHelper
 import tachiyomi.domain.source.model.Pin
 import tachiyomi.domain.source.model.Source
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
 import tachiyomi.presentation.core.components.material.padding
 import tachiyomi.presentation.core.components.material.topSmallPaddingValues
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import tachiyomi.presentation.core.theme.header
@@ -49,7 +49,7 @@ fun SourcesScreen(
     when {
         state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
         state.isEmpty -> EmptyScreen(
-            textResource = R.string.source_empty_screen,
+            stringRes = MR.strings.source_empty_screen,
             modifier = Modifier.padding(contentPadding),
         )
         else -> {
@@ -121,7 +121,7 @@ private fun SourceItem(
             if (source.supportsLatest) {
                 TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
                     Text(
-                        text = stringResource(R.string.latest),
+                        text = localize(MR.strings.latest),
                         style = LocalTextStyle.current.copy(
                             color = MaterialTheme.colorScheme.primary,
                         ),
@@ -149,12 +149,12 @@ private fun SourcePinButton(
             alpha = SecondaryItemAlpha,
         )
     }
-    val description = if (isPinned) R.string.action_unpin else R.string.action_pin
+    val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
     IconButton(onClick = onClick) {
         Icon(
             imageVector = icon,
             tint = tint,
-            contentDescription = stringResource(description),
+            contentDescription = localize(description),
         )
     }
 }
@@ -172,9 +172,9 @@ fun SourceOptionsDialog(
         },
         text = {
             Column {
-                val textId = if (Pin.Pinned in source.pin) R.string.action_unpin else R.string.action_pin
+                val textId = if (Pin.Pinned in source.pin) MR.strings.action_unpin else MR.strings.action_pin
                 Text(
-                    text = stringResource(textId),
+                    text = localize(textId),
                     modifier = Modifier
                         .clickable(onClick = onClickPin)
                         .fillMaxWidth()
@@ -182,7 +182,7 @@ fun SourceOptionsDialog(
                 )
                 if (!source.isLocal()) {
                     Text(
-                        text = stringResource(R.string.action_disable),
+                        text = localize(MR.strings.action_disable),
                         modifier = Modifier
                             .clickable(onClick = onClickDisable)
                             .fillMaxWidth()

+ 6 - 8
app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceDialogs.kt

@@ -4,11 +4,9 @@ import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun RemoveMangaDialog(
@@ -20,7 +18,7 @@ fun RemoveMangaDialog(
         onDismissRequest = onDismissRequest,
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -30,14 +28,14 @@ fun RemoveMangaDialog(
                     onConfirm()
                 },
             ) {
-                Text(text = stringResource(R.string.action_remove))
+                Text(text = localize(MR.strings.action_remove))
             }
         },
         title = {
-            Text(text = stringResource(R.string.are_you_sure))
+            Text(text = localize(MR.strings.are_you_sure))
         },
         text = {
-            Text(text = stringResource(R.string.remove_manga, mangaToRemove.title))
+            Text(text = localize(MR.strings.remove_manga, mangaToRemove.title))
         },
     )
 }

+ 9 - 9
app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt

@@ -10,18 +10,18 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.components.AppBarTitle
 import eu.kanade.presentation.components.DropdownMenu
 import eu.kanade.presentation.components.RadioMenuItem
 import eu.kanade.presentation.components.SearchToolbar
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.source.ConfigurableSource
 import eu.kanade.tachiyomi.source.Source
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.domain.library.model.LibraryDisplayMode
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.source.local.LocalSource
 
 @Composable
@@ -58,7 +58,7 @@ fun BrowseSourceToolbar(
                     .apply {
                         add(
                             AppBar.Action(
-                                title = stringResource(R.string.action_display_mode),
+                                title = localize(MR.strings.action_display_mode),
                                 icon = if (displayMode == LibraryDisplayMode.List) {
                                     Icons.AutoMirrored.Filled.ViewList
                                 } else {
@@ -70,14 +70,14 @@ fun BrowseSourceToolbar(
                         if (isLocalSource) {
                             add(
                                 AppBar.OverflowAction(
-                                    title = stringResource(R.string.label_help),
+                                    title = localize(MR.strings.label_help),
                                     onClick = onHelpClick,
                                 ),
                             )
                         } else {
                             add(
                                 AppBar.OverflowAction(
-                                    title = stringResource(R.string.action_open_in_web_view),
+                                    title = localize(MR.strings.action_open_in_web_view),
                                     onClick = onWebViewClick,
                                 ),
                             )
@@ -85,7 +85,7 @@ fun BrowseSourceToolbar(
                         if (isConfigurableSource) {
                             add(
                                 AppBar.OverflowAction(
-                                    title = stringResource(R.string.action_settings),
+                                    title = localize(MR.strings.action_settings),
                                     onClick = onSettingsClick,
                                 ),
                             )
@@ -99,21 +99,21 @@ fun BrowseSourceToolbar(
                 onDismissRequest = { selectingDisplayMode = false },
             ) {
                 RadioMenuItem(
-                    text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) },
+                    text = { Text(text = localize(MR.strings.action_display_comfortable_grid)) },
                     isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
                 ) {
                     selectingDisplayMode = false
                     onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
                 }
                 RadioMenuItem(
-                    text = { Text(text = stringResource(R.string.action_display_grid)) },
+                    text = { Text(text = localize(MR.strings.action_display_grid)) },
                     isChecked = displayMode == LibraryDisplayMode.CompactGrid,
                 ) {
                     selectingDisplayMode = false
                     onDisplayModeChange(LibraryDisplayMode.CompactGrid)
                 }
                 RadioMenuItem(
-                    text = { Text(text = stringResource(R.string.action_display_list)) },
+                    text = { Text(text = localize(MR.strings.action_display_list)) },
                     isChecked = displayMode == LibraryDisplayMode.List,
                 ) {
                     selectingDisplayMode = false

+ 3 - 3
app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchCardRow.kt

@@ -13,15 +13,15 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.library.components.CommonMangaItemDefaults
 import eu.kanade.presentation.library.components.MangaComfortableGridItem
-import eu.kanade.tachiyomi.R
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.manga.model.MangaCover
 import tachiyomi.domain.manga.model.asMangaCover
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun GlobalSearchCardRow(
@@ -78,7 +78,7 @@ private fun MangaItem(
 @Composable
 private fun EmptyResultItem() {
     Text(
-        text = stringResource(R.string.no_results_found),
+        text = localize(MR.strings.no_results_found),
         modifier = Modifier
             .padding(
                 horizontal = MaterialTheme.padding.medium,

+ 3 - 3
app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchResultItems.kt

@@ -22,11 +22,11 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun GlobalSearchResultItem(
@@ -93,7 +93,7 @@ fun GlobalSearchErrorResultItem(message: String?) {
         Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
         Spacer(Modifier.height(4.dp))
         Text(
-            text = message ?: stringResource(R.string.unknown_error),
+            text = message ?: localize(MR.strings.unknown_error),
             textAlign = TextAlign.Center,
         )
     }

+ 5 - 5
app/src/main/java/eu/kanade/presentation/browse/components/GlobalSearchToolbar.kt

@@ -26,11 +26,11 @@ import androidx.compose.material3.VerticalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.components.SearchToolbar
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun GlobalSearchToolbar(
@@ -85,7 +85,7 @@ fun GlobalSearchToolbar(
                     )
                 },
                 label = {
-                    Text(text = stringResource(id = R.string.pinned_sources))
+                    Text(text = localize(MR.strings.pinned_sources))
                 },
             )
             FilterChip(
@@ -100,7 +100,7 @@ fun GlobalSearchToolbar(
                     )
                 },
                 label = {
-                    Text(text = stringResource(id = R.string.all))
+                    Text(text = localize(MR.strings.all))
                 },
             )
 
@@ -118,7 +118,7 @@ fun GlobalSearchToolbar(
                     )
                 },
                 label = {
-                    Text(text = stringResource(id = R.string.has_results))
+                    Text(text = localize(MR.strings.has_results))
                 },
             )
         }

+ 5 - 4
app/src/main/java/eu/kanade/presentation/category/CategoryExtensions.kt

@@ -2,19 +2,20 @@ package eu.kanade.presentation.category
 
 import android.content.Context
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
+import tachiyomi.core.i18n.localize
 import tachiyomi.domain.category.model.Category
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 val Category.visualName: String
     @Composable
     get() = when {
-        isSystemCategory -> stringResource(R.string.label_default)
+        isSystemCategory -> localize(MR.strings.label_default)
         else -> name
     }
 
 fun Category.visualName(context: Context): String =
     when {
-        isSystemCategory -> context.getString(R.string.label_default)
+        isSystemCategory -> context.localize(MR.strings.label_default)
         else -> name
     }

+ 5 - 5
app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt

@@ -12,18 +12,18 @@ import androidx.compose.material.icons.outlined.SortByAlpha
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.category.components.CategoryFloatingActionButton
 import eu.kanade.presentation.category.components.CategoryListItem
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.category.CategoryScreenState
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.domain.category.model.Category
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.padding
 import tachiyomi.presentation.core.components.material.topSmallPaddingValues
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.util.plus
 
@@ -42,13 +42,13 @@ fun CategoryScreen(
     Scaffold(
         topBar = { scrollBehavior ->
             AppBar(
-                title = stringResource(R.string.action_edit_categories),
+                title = localize(MR.strings.action_edit_categories),
                 navigateUp = navigateUp,
                 actions = {
                     AppBarActions(
                         persistentListOf(
                             AppBar.Action(
-                                title = stringResource(R.string.action_sort),
+                                title = localize(MR.strings.action_sort),
                                 icon = Icons.Outlined.SortByAlpha,
                                 onClick = onClickSortAlphabetically,
                             ),
@@ -67,7 +67,7 @@ fun CategoryScreen(
     ) { paddingValues ->
         if (state.isEmpty) {
             EmptyScreen(
-                textResource = R.string.information_empty_category,
+                stringRes = MR.strings.information_empty_category,
                 modifier = Modifier.padding(paddingValues),
             )
             return@Scaffold

+ 31 - 31
app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt

@@ -25,14 +25,14 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.res.stringResource
 import eu.kanade.core.preference.asToggleableState
 import eu.kanade.presentation.category.visualName
-import eu.kanade.tachiyomi.R
 import kotlinx.coroutines.delay
 import tachiyomi.core.preference.CheckboxState
 import tachiyomi.domain.category.model.Category
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import kotlin.time.Duration.Companion.seconds
 
 @Composable
@@ -56,30 +56,30 @@ fun CategoryCreateDialog(
                     onDismissRequest()
                 },
             ) {
-                Text(text = stringResource(R.string.action_add))
+                Text(text = localize(MR.strings.action_add))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         title = {
-            Text(text = stringResource(R.string.action_add_category))
+            Text(text = localize(MR.strings.action_add_category))
         },
         text = {
             OutlinedTextField(
                 modifier = Modifier.focusRequester(focusRequester),
                 value = name,
                 onValueChange = { name = it },
-                label = { Text(text = stringResource(R.string.name)) },
+                label = { Text(text = localize(MR.strings.name)) },
                 supportingText = {
                     val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
-                        R.string.error_category_exists
+                        MR.strings.error_category_exists
                     } else {
-                        R.string.information_required_plain
+                        MR.strings.information_required_plain
                     }
-                    Text(text = stringResource(msgRes))
+                    Text(text = localize(msgRes))
                 },
                 isError = name.isNotEmpty() && nameAlreadyExists,
                 singleLine = true,
@@ -117,16 +117,16 @@ fun CategoryRenameDialog(
                     onDismissRequest()
                 },
             ) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         title = {
-            Text(text = stringResource(R.string.action_rename_category))
+            Text(text = localize(MR.strings.action_rename_category))
         },
         text = {
             OutlinedTextField(
@@ -136,14 +136,14 @@ fun CategoryRenameDialog(
                     valueHasChanged = name != it
                     name = it
                 },
-                label = { Text(text = stringResource(R.string.name)) },
+                label = { Text(text = localize(MR.strings.name)) },
                 supportingText = {
                     val msgRes = if (valueHasChanged && nameAlreadyExists) {
-                        R.string.error_category_exists
+                        MR.strings.error_category_exists
                     } else {
-                        R.string.information_required_plain
+                        MR.strings.information_required_plain
                     }
-                    Text(text = stringResource(msgRes))
+                    Text(text = localize(msgRes))
                 },
                 isError = valueHasChanged && nameAlreadyExists,
                 singleLine = true,
@@ -171,19 +171,19 @@ fun CategoryDeleteDialog(
                 onDelete()
                 onDismissRequest()
             }) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         title = {
-            Text(text = stringResource(R.string.delete_category))
+            Text(text = localize(MR.strings.delete_category))
         },
         text = {
-            Text(text = stringResource(R.string.delete_category_confirmation, category.name))
+            Text(text = localize(MR.strings.delete_category_confirmation, category.name))
         },
     )
 }
@@ -200,19 +200,19 @@ fun CategorySortAlphabeticallyDialog(
                 onSort()
                 onDismissRequest()
             }) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         title = {
-            Text(text = stringResource(R.string.action_sort_category))
+            Text(text = localize(MR.strings.action_sort_category))
         },
         text = {
-            Text(text = stringResource(R.string.sort_category_confirmation))
+            Text(text = localize(MR.strings.sort_category_confirmation))
         },
     )
 }
@@ -234,14 +234,14 @@ fun ChangeCategoryDialog(
                         onEditCategories()
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_edit_categories))
+                    Text(text = localize(MR.strings.action_edit_categories))
                 }
             },
             title = {
-                Text(text = stringResource(R.string.action_move_category))
+                Text(text = localize(MR.strings.action_move_category))
             },
             text = {
-                Text(text = stringResource(R.string.information_empty_category_dialog))
+                Text(text = localize(MR.strings.information_empty_category_dialog))
             },
         )
         return
@@ -255,11 +255,11 @@ fun ChangeCategoryDialog(
                     onDismissRequest()
                     onEditCategories()
                 }) {
-                    Text(text = stringResource(R.string.action_edit))
+                    Text(text = localize(MR.strings.action_edit))
                 }
                 Spacer(modifier = Modifier.weight(1f))
                 tachiyomi.presentation.core.components.material.TextButton(onClick = onDismissRequest) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
                 tachiyomi.presentation.core.components.material.TextButton(
                     onClick = {
@@ -274,12 +274,12 @@ fun ChangeCategoryDialog(
                         )
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_ok))
+                    Text(text = localize(MR.strings.action_ok))
                 }
             }
         },
         title = {
-            Text(text = stringResource(R.string.action_move_category))
+            Text(text = localize(MR.strings.action_move_category))
         },
         text = {
             Column(

+ 3 - 3
app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt

@@ -6,9 +6,9 @@ import androidx.compose.material.icons.outlined.Add
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrollingUp
 
@@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
     onCreate: () -> Unit,
 ) {
     ExtendedFloatingActionButton(
-        text = { Text(text = stringResource(R.string.action_add)) },
+        text = { Text(text = localize(MR.strings.action_add)) },
         icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
         onClick = onCreate,
         expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),

+ 4 - 4
app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt

@@ -20,10 +20,10 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
 import tachiyomi.domain.category.model.Category
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun CategoryListItem(
@@ -74,11 +74,11 @@ fun CategoryListItem(
             IconButton(onClick = onRename) {
                 Icon(
                     imageVector = Icons.Outlined.Edit,
-                    contentDescription = stringResource(R.string.action_rename_category),
+                    contentDescription = localize(MR.strings.action_rename_category),
                 )
             }
             IconButton(onClick = onDelete) {
-                Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
+                Icon(imageVector = Icons.Outlined.Delete, contentDescription = localize(MR.strings.action_delete))
             }
         }
     }

+ 9 - 7
app/src/main/java/eu/kanade/presentation/components/AppBar.kt

@@ -52,6 +52,8 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.ImmutableList
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
 import tachiyomi.presentation.core.util.runOnEnterKeyPressed
 import tachiyomi.presentation.core.util.secondaryItemAlpha
@@ -133,7 +135,7 @@ fun AppBar(
                     IconButton(onClick = onCancelActionMode) {
                         Icon(
                             imageVector = Icons.Outlined.Close,
-                            contentDescription = stringResource(R.string.action_cancel),
+                            contentDescription = localize(MR.strings.action_cancel),
                         )
                     }
                 } else {
@@ -253,7 +255,7 @@ fun AppBarActions(
 /**
  * @param searchEnabled Set to false if you don't want to show search action.
  * @param searchQuery If null, use normal toolbar.
- * @param placeholderText If null, [R.string.action_search_hint] is used.
+ * @param placeholderText If null, [MR.strings.action_search_hint] is used.
  */
 @Composable
 fun SearchToolbar(
@@ -317,7 +319,7 @@ fun SearchToolbar(
                         placeholder = {
                             Text(
                                 modifier = Modifier.secondaryItemAlpha(),
-                                text = (placeholderText ?: stringResource(R.string.action_search_hint)),
+                                text = (placeholderText ?: localize(MR.strings.action_search_hint)),
                                 maxLines = 1,
                                 overflow = TextOverflow.Ellipsis,
                                 style = MaterialTheme.typography.titleMedium.copy(
@@ -342,7 +344,7 @@ fun SearchToolbar(
                         positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
                         tooltip = {
                             PlainTooltip {
-                                Text(stringResource(R.string.action_search))
+                                Text(localize(MR.strings.action_search))
                             }
                         },
                         state = rememberTooltipState(),
@@ -352,7 +354,7 @@ fun SearchToolbar(
                         ) {
                             Icon(
                                 Icons.Outlined.Search,
-                                contentDescription = stringResource(R.string.action_search),
+                                contentDescription = localize(MR.strings.action_search),
                             )
                         }
                     }
@@ -361,7 +363,7 @@ fun SearchToolbar(
                         positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
                         tooltip = {
                             PlainTooltip {
-                                Text(stringResource(R.string.action_reset))
+                                Text(localize(MR.strings.action_reset))
                             }
                         },
                         state = rememberTooltipState(),
@@ -374,7 +376,7 @@ fun SearchToolbar(
                         ) {
                             Icon(
                                 Icons.Outlined.Close,
-                                contentDescription = stringResource(R.string.action_reset),
+                                contentDescription = localize(MR.strings.action_reset),
                             )
                         }
                     }

+ 8 - 8
app/src/main/java/eu/kanade/presentation/components/Banners.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.components
 
-import androidx.annotation.StringRes
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.shrinkVertically
@@ -26,13 +25,14 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastMaxBy
-import eu.kanade.tachiyomi.R
+import dev.icerock.moko.resources.StringResource
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 val DownloadedOnlyBannerBackgroundColor
     @Composable get() = MaterialTheme.colorScheme.tertiary
@@ -43,11 +43,11 @@ val IndexingBannerBackgroundColor
 
 @Composable
 fun WarningBanner(
-    @StringRes textRes: Int,
+    textRes: StringResource,
     modifier: Modifier = Modifier,
 ) {
     Text(
-        text = stringResource(textRes),
+        text = localize(textRes),
         modifier = modifier
             .fillMaxWidth()
             .background(MaterialTheme.colorScheme.error)
@@ -127,7 +127,7 @@ fun AppStateBanners(
 @Composable
 private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
     Text(
-        text = stringResource(R.string.label_downloaded_only),
+        text = localize(MR.strings.label_downloaded_only),
         modifier = Modifier
             .background(DownloadedOnlyBannerBackgroundColor)
             .fillMaxWidth()
@@ -142,7 +142,7 @@ private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
 @Composable
 private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
     Text(
-        text = stringResource(R.string.pref_incognito_mode),
+        text = localize(MR.strings.pref_incognito_mode),
         modifier = Modifier
             .background(IncognitoModeBannerBackgroundColor)
             .fillMaxWidth()
@@ -173,7 +173,7 @@ private fun IndexingDownloadBanner(modifier: Modifier = Modifier) {
         )
         Spacer(modifier = Modifier.width(8.dp))
         Text(
-            text = stringResource(R.string.download_notifier_cache_renewal),
+            text = localize(MR.strings.download_notifier_cache_renewal),
             color = MaterialTheme.colorScheme.onSecondary,
             textAlign = TextAlign.Center,
             style = MaterialTheme.typography.labelMedium,

+ 8 - 8
app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt

@@ -3,10 +3,10 @@ package eu.kanade.presentation.components
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.manga.DownloadAction
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 
 @Composable
 fun DownloadDropdownMenu(
@@ -19,11 +19,11 @@ fun DownloadDropdownMenu(
         onDismissRequest = onDismissRequest,
     ) {
         listOfNotNull(
-            DownloadAction.NEXT_1_CHAPTER to pluralStringResource(R.plurals.download_amount, 1, 1),
-            DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(R.plurals.download_amount, 5, 5),
-            DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(R.plurals.download_amount, 10, 10),
-            DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(R.plurals.download_amount, 25, 25),
-            DownloadAction.UNREAD_CHAPTERS to stringResource(R.string.download_unread),
+            DownloadAction.NEXT_1_CHAPTER to localizePlural(MR.plurals.download_amount, 1, 1),
+            DownloadAction.NEXT_5_CHAPTERS to localizePlural(MR.plurals.download_amount, 5, 5),
+            DownloadAction.NEXT_10_CHAPTERS to localizePlural(MR.plurals.download_amount, 10, 10),
+            DownloadAction.NEXT_25_CHAPTERS to localizePlural(MR.plurals.download_amount, 25, 25),
+            DownloadAction.UNREAD_CHAPTERS to localize(MR.strings.download_unread),
         ).map { (downloadAction, string) ->
             DropdownMenuItem(
                 text = { Text(text = string) },

+ 4 - 4
app/src/main/java/eu/kanade/presentation/components/DropdownMenu.kt

@@ -15,11 +15,11 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.PopupProperties
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
 
 @Composable
@@ -54,13 +54,13 @@ fun RadioMenuItem(
             if (isChecked) {
                 Icon(
                     imageVector = Icons.Outlined.RadioButtonChecked,
-                    contentDescription = stringResource(R.string.selected),
+                    contentDescription = localize(MR.strings.selected),
                     tint = MaterialTheme.colorScheme.primary,
                 )
             } else {
                 Icon(
                     imageVector = Icons.Outlined.RadioButtonUnchecked,
-                    contentDescription = stringResource(R.string.not_selected),
+                    contentDescription = localize(MR.strings.not_selected),
                 )
             }
         },

+ 5 - 6
app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt

@@ -2,14 +2,13 @@ package eu.kanade.presentation.components
 
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.HelpOutline
-import androidx.compose.material.icons.outlined.HelpOutline
 import androidx.compose.material.icons.outlined.Refresh
 import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.EmptyScreenAction
 
@@ -19,7 +18,7 @@ private fun NoActionPreview() {
     TachiyomiTheme {
         Surface {
             EmptyScreen(
-                textResource = R.string.empty_screen,
+                stringRes = MR.strings.empty_screen,
             )
         }
     }
@@ -31,15 +30,15 @@ private fun WithActionPreview() {
     TachiyomiTheme {
         Surface {
             EmptyScreen(
-                textResource = R.string.empty_screen,
+                stringRes = MR.strings.empty_screen,
                 actions = persistentListOf(
                     EmptyScreenAction(
-                        stringResId = R.string.action_retry,
+                        stringRes = MR.strings.action_retry,
                         icon = Icons.Outlined.Refresh,
                         onClick = {},
                     ),
                     EmptyScreenAction(
-                        stringResId = R.string.getting_started_guide,
+                        stringRes = MR.strings.getting_started_guide,
                         icon = Icons.AutoMirrored.Outlined.HelpOutline,
                         onClick = {},
                     ),

+ 3 - 3
app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt

@@ -24,14 +24,14 @@ import androidx.compose.runtime.rememberCoroutineScope
 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.util.fastForEachIndexed
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.coroutines.launch
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.HorizontalPager
 import tachiyomi.presentation.core.components.material.TabText
+import tachiyomi.presentation.core.i18n.localize
 
 object TabbedDialogPaddings {
     val Horizontal = 24.dp
@@ -94,7 +94,7 @@ private fun MoreMenu(
         IconButton(onClick = { expanded = true }) {
             Icon(
                 imageVector = Icons.Default.MoreVert,
-                contentDescription = stringResource(R.string.label_more),
+                contentDescription = localize(MR.strings.label_more),
             )
         }
         DropdownMenu(

+ 6 - 6
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.components
 
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -20,17 +19,18 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
+import dev.icerock.moko.resources.StringResource
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.launch
 import tachiyomi.presentation.core.components.HorizontalPager
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.TabText
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun TabbedScreen(
-    @StringRes titleRes: Int,
+    titleRes: StringResource,
     tabs: ImmutableList<TabContent>,
     startIndex: Int? = null,
     searchQuery: String? = null,
@@ -52,7 +52,7 @@ fun TabbedScreen(
             val searchEnabled = tab.searchEnabled
 
             SearchToolbar(
-                titleContent = { AppBarTitle(stringResource(titleRes)) },
+                titleContent = { AppBarTitle(localize(titleRes)) },
                 searchEnabled = searchEnabled,
                 searchQuery = if (searchEnabled) searchQuery else null,
                 onChangeSearchQuery = onChangeSearchQuery,
@@ -75,7 +75,7 @@ fun TabbedScreen(
                     Tab(
                         selected = state.currentPage == index,
                         onClick = { scope.launch { state.animateScrollToPage(index) } },
-                        text = { TabText(text = stringResource(tab.titleRes), badgeCount = tab.badgeNumber) },
+                        text = { TabText(text = localize(tab.titleRes), badgeCount = tab.badgeNumber) },
                         unselectedContentColor = MaterialTheme.colorScheme.onSurface,
                     )
                 }
@@ -96,7 +96,7 @@ fun TabbedScreen(
 }
 
 data class TabContent(
-    @StringRes val titleRes: Int,
+    val titleRes: StringResource,
     val badgeNumber: Int? = null,
     val searchEnabled: Boolean = false,
     val actions: ImmutableList<AppBar.Action> = persistentListOf(),

+ 6 - 6
app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt

@@ -13,13 +13,13 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.CrashLogUtil
 import kotlinx.coroutines.launch
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.InfoScreen
 
 @Composable
@@ -32,15 +32,15 @@ fun CrashScreen(
 
     InfoScreen(
         icon = Icons.Outlined.BugReport,
-        headingText = stringResource(R.string.crash_screen_title),
-        subtitleText = stringResource(R.string.crash_screen_description, stringResource(R.string.app_name)),
-        acceptText = stringResource(R.string.pref_dump_crash_logs),
+        headingText = localize(MR.strings.crash_screen_title),
+        subtitleText = localize(MR.strings.crash_screen_description, localize(MR.strings.app_name)),
+        acceptText = localize(MR.strings.pref_dump_crash_logs),
         onAcceptClick = {
             scope.launch {
                 CrashLogUtil(context).dumpLogs()
             }
         },
-        rejectText = stringResource(R.string.crash_screen_restart_application),
+        rejectText = localize(MR.strings.crash_screen_restart_application),
         onRejectClick = onRestartClick,
     ) {
         Box(

+ 7 - 7
app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt

@@ -10,7 +10,6 @@ import androidx.compose.material3.SnackbarHostState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.tooling.preview.PreviewParameter
 import eu.kanade.domain.ui.UiPreferences
@@ -21,13 +20,14 @@ import eu.kanade.presentation.components.RelativeDateHeader
 import eu.kanade.presentation.components.SearchToolbar
 import eu.kanade.presentation.history.components.HistoryItem
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.core.preference.InMemoryPreferenceStore
 import tachiyomi.domain.history.model.HistoryWithRelations
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.FastScrollLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import uy.kohesive.injekt.Injekt
@@ -47,14 +47,14 @@ fun HistoryScreen(
     Scaffold(
         topBar = { scrollBehavior ->
             SearchToolbar(
-                titleContent = { AppBarTitle(stringResource(R.string.history)) },
+                titleContent = { AppBarTitle(localize(MR.strings.history)) },
                 searchQuery = state.searchQuery,
                 onChangeSearchQuery = onSearchQueryChange,
                 actions = {
                     AppBarActions(
                         persistentListOf(
                             AppBar.Action(
-                                title = stringResource(R.string.pref_clear_history),
+                                title = localize(MR.strings.pref_clear_history),
                                 icon = Icons.Outlined.DeleteSweep,
                                 onClick = {
                                     onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
@@ -73,12 +73,12 @@ fun HistoryScreen(
                 LoadingScreen(Modifier.padding(contentPadding))
             } else if (it.isEmpty()) {
                 val msg = if (!state.searchQuery.isNullOrEmpty()) {
-                    R.string.no_results_found
+                    MR.strings.no_results_found
                 } else {
-                    R.string.information_no_recent_manga
+                    MR.strings.information_no_recent_manga
                 }
                 EmptyScreen(
-                    textResource = msg,
+                    stringRes = msg,
                     modifier = Modifier.padding(contentPadding),
                 )
             } else {

+ 11 - 11
app/src/main/java/eu/kanade/presentation/history/components/HistoryDialogs.kt

@@ -10,12 +10,12 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun HistoryDeleteDialog(
@@ -26,16 +26,16 @@ fun HistoryDeleteDialog(
 
     AlertDialog(
         title = {
-            Text(text = stringResource(R.string.action_remove))
+            Text(text = localize(MR.strings.action_remove))
         },
         text = {
             Column(
                 verticalArrangement = Arrangement.spacedBy(8.dp),
             ) {
-                Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
+                Text(text = localize(MR.strings.dialog_with_checkbox_remove_description))
 
                 LabeledCheckbox(
-                    label = stringResource(R.string.dialog_with_checkbox_reset),
+                    label = localize(MR.strings.dialog_with_checkbox_reset),
                     checked = removeEverything,
                     onCheckedChange = { removeEverything = it },
                 )
@@ -47,12 +47,12 @@ fun HistoryDeleteDialog(
                 onDelete(removeEverything)
                 onDismissRequest()
             }) {
-                Text(text = stringResource(R.string.action_remove))
+                Text(text = localize(MR.strings.action_remove))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
     )
@@ -65,10 +65,10 @@ fun HistoryDeleteAllDialog(
 ) {
     AlertDialog(
         title = {
-            Text(text = stringResource(R.string.action_remove_everything))
+            Text(text = localize(MR.strings.action_remove_everything))
         },
         text = {
-            Text(text = stringResource(R.string.clear_history_confirmation))
+            Text(text = localize(MR.strings.clear_history_confirmation))
         },
         onDismissRequest = onDismissRequest,
         confirmButton = {
@@ -76,12 +76,12 @@ fun HistoryDeleteAllDialog(
                 onDelete()
                 onDismissRequest()
             }) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
     )

+ 5 - 5
app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt

@@ -17,7 +17,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.tooling.preview.PreviewLightDark
@@ -26,10 +25,11 @@ import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.manga.components.MangaCover
 import eu.kanade.presentation.theme.TachiyomiTheme
 import eu.kanade.presentation.util.formatChapterNumber
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.lang.toTimestampString
 import tachiyomi.domain.history.model.HistoryWithRelations
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 private val HistoryItemHeight = 96.dp
 
@@ -69,8 +69,8 @@ fun HistoryItem(
             val readAt = remember { history.readAt?.toTimestampString() ?: "" }
             Text(
                 text = if (history.chapterNumber > -1) {
-                    stringResource(
-                        R.string.recent_manga_time,
+                    localize(
+                        MR.strings.recent_manga_time,
                         formatChapterNumber(history.chapterNumber),
                         readAt,
                     )
@@ -85,7 +85,7 @@ fun HistoryItem(
         IconButton(onClick = onClickDelete) {
             Icon(
                 imageVector = Icons.Outlined.Delete,
-                contentDescription = stringResource(R.string.action_delete),
+                contentDescription = localize(MR.strings.action_delete),
                 tint = MaterialTheme.colorScheme.onSurface,
             )
         }

+ 11 - 10
app/src/main/java/eu/kanade/presentation/library/DeleteLibraryMangaDialog.kt

@@ -9,10 +9,11 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
+import dev.icerock.moko.resources.StringResource
 import tachiyomi.core.preference.CheckboxState
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun DeleteLibraryMangaDialog(
@@ -22,10 +23,10 @@ fun DeleteLibraryMangaDialog(
 ) {
     var list by remember {
         mutableStateOf(
-            buildList<CheckboxState.State<Int>> {
-                add(CheckboxState.State.None(R.string.manga_from_library))
+            buildList<CheckboxState.State<StringResource>> {
+                add(CheckboxState.State.None(MR.strings.manga_from_library))
                 if (!containsLocalManga) {
-                    add(CheckboxState.State.None(R.string.downloaded_chapters))
+                    add(CheckboxState.State.None(MR.strings.downloaded_chapters))
                 }
             },
         )
@@ -34,7 +35,7 @@ fun DeleteLibraryMangaDialog(
         onDismissRequest = onDismissRequest,
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -48,23 +49,23 @@ fun DeleteLibraryMangaDialog(
                     )
                 },
             ) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         title = {
-            Text(text = stringResource(R.string.action_remove))
+            Text(text = localize(MR.strings.action_remove))
         },
         text = {
             Column {
                 list.forEach { state ->
                     LabeledCheckbox(
-                        label = stringResource(state.value),
+                        label = localize(state.value),
                         checked = state.isChecked,
                         onCheckedChange = {
                             val index = list.indexOf(state)
                             if (index != -1) {
                                 val mutableList = list.toMutableList()
-                                mutableList[index] = state.next() as CheckboxState.State<Int>
+                                mutableList[index] = state.next() as CheckboxState.State<StringResource>
                                 list = mutableList.toList()
                             }
                         },

+ 39 - 39
app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt

@@ -13,10 +13,8 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.components.TabbedDialog
 import eu.kanade.presentation.components.TabbedDialogPaddings
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.core.preference.TriState
@@ -25,12 +23,14 @@ import tachiyomi.domain.library.model.LibraryDisplayMode
 import tachiyomi.domain.library.model.LibrarySort
 import tachiyomi.domain.library.model.sort
 import tachiyomi.domain.library.service.LibraryPreferences
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.CheckboxItem
 import tachiyomi.presentation.core.components.HeadingItem
 import tachiyomi.presentation.core.components.SettingsChipRow
 import tachiyomi.presentation.core.components.SliderItem
 import tachiyomi.presentation.core.components.SortItem
 import tachiyomi.presentation.core.components.TriStateItem
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 
 @Composable
@@ -42,9 +42,9 @@ fun LibrarySettingsDialog(
     TabbedDialog(
         onDismissRequest = onDismissRequest,
         tabTitles = persistentListOf(
-            stringResource(R.string.action_filter),
-            stringResource(R.string.action_sort),
-            stringResource(R.string.action_display),
+            localize(MR.strings.action_filter),
+            localize(MR.strings.action_sort),
+            localize(MR.strings.action_display),
         ),
     ) { page ->
         Column(
@@ -75,7 +75,7 @@ private fun ColumnScope.FilterPage(
     val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
     val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
     TriStateItem(
-        label = stringResource(R.string.label_downloaded),
+        label = localize(MR.strings.label_downloaded),
         state = if (downloadedOnly) {
             TriState.ENABLED_IS
         } else {
@@ -86,25 +86,25 @@ private fun ColumnScope.FilterPage(
     )
     val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
     TriStateItem(
-        label = stringResource(R.string.action_filter_unread),
+        label = localize(MR.strings.action_filter_unread),
         state = filterUnread,
         onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
     )
     val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
     TriStateItem(
-        label = stringResource(R.string.label_started),
+        label = localize(MR.strings.label_started),
         state = filterStarted,
         onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
     )
     val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
     TriStateItem(
-        label = stringResource(R.string.action_filter_bookmarked),
+        label = localize(MR.strings.action_filter_bookmarked),
         state = filterBookmarked,
         onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
     )
     val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
     TriStateItem(
-        label = stringResource(R.string.completed),
+        label = localize(MR.strings.completed),
         state = filterCompleted,
         onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
     )
@@ -118,13 +118,13 @@ private fun ColumnScope.FilterPage(
             val service = trackers[0]
             val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
             TriStateItem(
-                label = stringResource(R.string.action_filter_tracked),
+                label = localize(MR.strings.action_filter_tracked),
                 state = filterTracker,
                 onClick = { screenModel.toggleTracker(service.id.toInt()) },
             )
         }
         else -> {
-            HeadingItem(R.string.action_filter_tracked)
+            HeadingItem(MR.strings.action_filter_tracked)
             trackers.map { service ->
                 val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
                 TriStateItem(
@@ -149,21 +149,21 @@ private fun ColumnScope.SortPage(
         if (screenModel.trackers.isEmpty()) {
             emptyList()
         } else {
-            listOf(R.string.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
+            listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
         }
 
     listOf(
-        R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
-        R.string.action_sort_total to LibrarySort.Type.TotalChapters,
-        R.string.action_sort_last_read to LibrarySort.Type.LastRead,
-        R.string.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
-        R.string.action_sort_unread_count to LibrarySort.Type.UnreadCount,
-        R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
-        R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
-        R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
+        MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
+        MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
+        MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
+        MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
+        MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
+        MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
+        MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
+        MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
     ).plus(trackerSortOption).map { (titleRes, mode) ->
         SortItem(
-            label = stringResource(titleRes),
+            label = localize(titleRes),
             sortDescending = sortDescending.takeIf { sortingMode == mode },
             onClick = {
                 val isTogglingDirection = sortingMode == mode
@@ -186,10 +186,10 @@ private fun ColumnScope.SortPage(
 }
 
 private val displayModes = listOf(
-    R.string.action_display_grid to LibraryDisplayMode.CompactGrid,
-    R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
-    R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
-    R.string.action_display_list to LibraryDisplayMode.List,
+    MR.strings.action_display_grid to LibraryDisplayMode.CompactGrid,
+    MR.strings.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
+    MR.strings.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
+    MR.strings.action_display_list to LibraryDisplayMode.List,
 )
 
 @Composable
@@ -197,12 +197,12 @@ private fun ColumnScope.DisplayPage(
     screenModel: LibrarySettingsScreenModel,
 ) {
     val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
-    SettingsChipRow(R.string.action_display_mode) {
+    SettingsChipRow(MR.strings.action_display_mode) {
         displayModes.map { (titleRes, mode) ->
             FilterChip(
                 selected = displayMode == mode,
                 onClick = { screenModel.setDisplayMode(mode) },
-                label = { Text(stringResource(titleRes)) },
+                label = { Text(localize(titleRes)) },
             )
         }
     }
@@ -219,43 +219,43 @@ private fun ColumnScope.DisplayPage(
 
         val columns by columnPreference.collectAsState()
         SliderItem(
-            label = stringResource(R.string.pref_library_columns),
+            label = localize(MR.strings.pref_library_columns),
             max = 10,
             value = columns,
             valueText = if (columns > 0) {
-                stringResource(R.string.pref_library_columns_per_row, columns)
+                localize(MR.strings.pref_library_columns_per_row, columns)
             } else {
-                stringResource(R.string.label_default)
+                localize(MR.strings.label_default)
             },
             onChange = columnPreference::set,
         )
     }
 
-    HeadingItem(R.string.overlay_header)
+    HeadingItem(MR.strings.overlay_header)
     CheckboxItem(
-        label = stringResource(R.string.action_display_download_badge),
+        label = localize(MR.strings.action_display_download_badge),
         pref = screenModel.libraryPreferences.downloadBadge(),
     )
     CheckboxItem(
-        label = stringResource(R.string.action_display_local_badge),
+        label = localize(MR.strings.action_display_local_badge),
         pref = screenModel.libraryPreferences.localBadge(),
     )
     CheckboxItem(
-        label = stringResource(R.string.action_display_language_badge),
+        label = localize(MR.strings.action_display_language_badge),
         pref = screenModel.libraryPreferences.languageBadge(),
     )
     CheckboxItem(
-        label = stringResource(R.string.action_display_show_continue_reading_button),
+        label = localize(MR.strings.action_display_show_continue_reading_button),
         pref = screenModel.libraryPreferences.showContinueReadingButton(),
     )
 
-    HeadingItem(R.string.tabs_header)
+    HeadingItem(MR.strings.tabs_header)
     CheckboxItem(
-        label = stringResource(R.string.action_display_show_tabs),
+        label = localize(MR.strings.action_display_show_tabs),
         pref = screenModel.libraryPreferences.categoryTabs(),
     )
     CheckboxItem(
-        label = stringResource(R.string.action_display_show_number_of_items),
+        label = localize(MR.strings.action_display_show_number_of_items),
         pref = screenModel.libraryPreferences.categoryNumberOfItems(),
     )
 }

+ 3 - 3
app/src/main/java/eu/kanade/presentation/library/components/CommonMangaItem.kt

@@ -33,14 +33,14 @@ import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import eu.kanade.presentation.manga.components.MangaCover
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.BadgeGroup
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.selectedBackground
 
 object CommonMangaItemDefaults {
@@ -378,7 +378,7 @@ private fun ContinueReadingButton(
         ) {
             Icon(
                 imageVector = Icons.Filled.PlayArrow,
-                contentDescription = stringResource(R.string.action_resume),
+                contentDescription = localize(MR.strings.action_resume),
                 modifier = Modifier.size(16.dp),
             )
         }

+ 3 - 3
app/src/main/java/eu/kanade/presentation/library/components/GlobalSearchItem.kt

@@ -4,9 +4,9 @@ import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.zIndex
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 internal fun GlobalSearchItem(
@@ -19,7 +19,7 @@ internal fun GlobalSearchItem(
         onClick = onClick,
     ) {
         Text(
-            text = stringResource(R.string.action_global_search_query, searchQuery),
+            text = localize(MR.strings.action_global_search_query, searchQuery),
             modifier = Modifier.zIndex(99f),
         )
     }

+ 5 - 5
app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt

@@ -18,10 +18,10 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.unit.dp
 import eu.kanade.core.preference.PreferenceMutableState
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.library.LibraryItem
 import tachiyomi.domain.library.model.LibraryDisplayMode
 import tachiyomi.domain.library.model.LibraryManga
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.HorizontalPager
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.util.plus
@@ -124,9 +124,9 @@ private fun LibraryPagerEmptyScreen(
     onGlobalSearchClicked: () -> Unit,
 ) {
     val msg = when {
-        !searchQuery.isNullOrEmpty() -> R.string.no_results_found
-        hasActiveFilters -> R.string.error_no_match
-        else -> R.string.information_no_manga_category
+        !searchQuery.isNullOrEmpty() -> MR.strings.no_results_found
+        hasActiveFilters -> MR.strings.error_no_match
+        else -> MR.strings.information_no_manga_category
     }
 
     Column(
@@ -146,7 +146,7 @@ private fun LibraryPagerEmptyScreen(
         }
 
         EmptyScreen(
-            textResource = msg,
+            stringRes = msg,
             modifier = Modifier.weight(1f),
         )
     }

+ 8 - 8
app/src/main/java/eu/kanade/presentation/library/components/LibraryToolbar.kt

@@ -14,15 +14,15 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.sp
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.components.SearchToolbar
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.Pill
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.theme.active
 
 @Composable
@@ -98,21 +98,21 @@ private fun LibraryRegularToolbar(
             AppBarActions(
                 persistentListOf(
                     AppBar.Action(
-                        title = stringResource(R.string.action_filter),
+                        title = localize(MR.strings.action_filter),
                         icon = Icons.Outlined.FilterList,
                         iconTint = filterTint,
                         onClick = onClickFilter,
                     ),
                     AppBar.OverflowAction(
-                        title = stringResource(R.string.action_update_library),
+                        title = localize(MR.strings.action_update_library),
                         onClick = onClickGlobalUpdate,
                     ),
                     AppBar.OverflowAction(
-                        title = stringResource(R.string.action_update_category),
+                        title = localize(MR.strings.action_update_category),
                         onClick = onClickRefresh,
                     ),
                     AppBar.OverflowAction(
-                        title = stringResource(R.string.action_open_random_manga),
+                        title = localize(MR.strings.action_open_random_manga),
                         onClick = onClickOpenRandomManga,
                     ),
                 ),
@@ -135,12 +135,12 @@ private fun LibrarySelectionToolbar(
             AppBarActions(
                 persistentListOf(
                     AppBar.Action(
-                        title = stringResource(R.string.action_select_all),
+                        title = localize(MR.strings.action_select_all),
                         icon = Icons.Outlined.SelectAll,
                         onClick = onClickSelectAll,
                     ),
                     AppBar.Action(
-                        title = stringResource(R.string.action_select_inverse),
+                        title = localize(MR.strings.action_select_inverse),
                         icon = Icons.Outlined.FlipToBack,
                         onClick = onClickInvertSelection,
                     ),

+ 24 - 24
app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt

@@ -25,20 +25,20 @@ import androidx.compose.runtime.saveable.rememberSaveable
 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 eu.kanade.domain.manga.model.downloadedFilter
 import eu.kanade.domain.manga.model.forceDownloaded
 import eu.kanade.presentation.components.TabbedDialog
 import eu.kanade.presentation.components.TabbedDialogPaddings
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.core.preference.TriState
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
 import tachiyomi.presentation.core.components.RadioItem
 import tachiyomi.presentation.core.components.SortItem
 import tachiyomi.presentation.core.components.TriStateItem
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.theme.active
 
 @Composable
@@ -66,20 +66,20 @@ fun ChapterSettingsDialog(
     TabbedDialog(
         onDismissRequest = onDismissRequest,
         tabTitles = persistentListOf(
-            stringResource(R.string.action_filter),
-            stringResource(R.string.action_sort),
-            stringResource(R.string.action_display),
+            localize(MR.strings.action_filter),
+            localize(MR.strings.action_sort),
+            localize(MR.strings.action_display),
         ),
         tabOverflowMenuContent = { closeMenu ->
             DropdownMenuItem(
-                text = { Text(stringResource(R.string.set_chapter_settings_as_default)) },
+                text = { Text(localize(MR.strings.set_chapter_settings_as_default)) },
                 onClick = {
                     showSetAsDefaultDialog = true
                     closeMenu()
                 },
             )
             DropdownMenuItem(
-                text = { Text(stringResource(R.string.action_reset)) },
+                text = { Text(localize(MR.strings.action_reset)) },
                 onClick = {
                     onResetToDefault()
                     closeMenu()
@@ -136,17 +136,17 @@ private fun ColumnScope.FilterPage(
     onScanlatorFilterClicked: (() -> Unit),
 ) {
     TriStateItem(
-        label = stringResource(R.string.label_downloaded),
+        label = localize(MR.strings.label_downloaded),
         state = downloadFilter,
         onClick = onDownloadFilterChanged,
     )
     TriStateItem(
-        label = stringResource(R.string.action_filter_unread),
+        label = localize(MR.strings.action_filter_unread),
         state = unreadFilter,
         onClick = onUnreadFilterChanged,
     )
     TriStateItem(
-        label = stringResource(R.string.action_filter_bookmarked),
+        label = localize(MR.strings.action_filter_bookmarked),
         state = bookmarkedFilter,
         onClick = onBookmarkedFilterChanged,
     )
@@ -179,7 +179,7 @@ fun ScanlatorFilterItem(
             },
         )
         Text(
-            text = stringResource(R.string.scanlator),
+            text = localize(MR.strings.scanlator),
             style = MaterialTheme.typography.bodyMedium,
         )
     }
@@ -192,13 +192,13 @@ private fun ColumnScope.SortPage(
     onItemSelected: (Long) -> Unit,
 ) {
     listOf(
-        R.string.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
-        R.string.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
-        R.string.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
-        R.string.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
+        MR.strings.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
+        MR.strings.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
+        MR.strings.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
+        MR.strings.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
     ).map { (titleRes, mode) ->
         SortItem(
-            label = stringResource(titleRes),
+            label = localize(titleRes),
             sortDescending = sortDescending.takeIf { sortingMode == mode },
             onClick = { onItemSelected(mode) },
         )
@@ -211,11 +211,11 @@ private fun ColumnScope.DisplayPage(
     onItemSelected: (Long) -> Unit,
 ) {
     listOf(
-        R.string.show_title to Manga.CHAPTER_DISPLAY_NAME,
-        R.string.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
+        MR.strings.show_title to Manga.CHAPTER_DISPLAY_NAME,
+        MR.strings.show_chapter_number to Manga.CHAPTER_DISPLAY_NUMBER,
     ).map { (titleRes, mode) ->
         RadioItem(
-            label = stringResource(titleRes),
+            label = localize(titleRes),
             selected = displayMode == mode,
             onClick = { onItemSelected(mode) },
         )
@@ -231,15 +231,15 @@ private fun SetAsDefaultDialog(
 
     AlertDialog(
         onDismissRequest = onDismissRequest,
-        title = { Text(text = stringResource(R.string.chapter_settings)) },
+        title = { Text(text = localize(MR.strings.chapter_settings)) },
         text = {
             Column(
                 verticalArrangement = Arrangement.spacedBy(12.dp),
             ) {
-                Text(text = stringResource(R.string.confirm_set_chapter_settings))
+                Text(text = localize(MR.strings.confirm_set_chapter_settings))
 
                 LabeledCheckbox(
-                    label = stringResource(R.string.also_set_chapter_settings_for_library),
+                    label = localize(MR.strings.also_set_chapter_settings_for_library),
                     checked = optionalChecked,
                     onCheckedChange = { optionalChecked = it },
                 )
@@ -247,7 +247,7 @@ private fun SetAsDefaultDialog(
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -257,7 +257,7 @@ private fun SetAsDefaultDialog(
                     onDismissRequest()
                 },
             ) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
     )

+ 7 - 7
app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt

@@ -8,9 +8,9 @@ import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun DuplicateMangaDialog(
@@ -21,10 +21,10 @@ fun DuplicateMangaDialog(
     AlertDialog(
         onDismissRequest = onDismissRequest,
         title = {
-            Text(text = stringResource(R.string.are_you_sure))
+            Text(text = localize(MR.strings.are_you_sure))
         },
         text = {
-            Text(text = stringResource(R.string.confirm_add_duplicate_manga))
+            Text(text = localize(MR.strings.confirm_add_duplicate_manga))
         },
         confirmButton = {
             FlowRow(
@@ -36,13 +36,13 @@ fun DuplicateMangaDialog(
                         onOpenManga()
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_show_manga))
+                    Text(text = localize(MR.strings.action_show_manga))
                 }
 
                 Spacer(modifier = Modifier.weight(1f))
 
                 TextButton(onClick = onDismissRequest) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
                 TextButton(
                     onClick = {
@@ -50,7 +50,7 @@ fun DuplicateMangaDialog(
                         onConfirm()
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_add))
+                    Text(text = localize(MR.strings.action_add))
                 }
             }
         },

+ 8 - 8
app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt

@@ -44,7 +44,6 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastMap
@@ -58,7 +57,6 @@ import eu.kanade.presentation.manga.components.MangaInfoBox
 import eu.kanade.presentation.manga.components.MangaToolbar
 import eu.kanade.presentation.manga.components.MissingChapterCountListItem
 import eu.kanade.presentation.util.formatChapterNumber
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.source.getNameForMangaInfo
 import eu.kanade.tachiyomi.ui.manga.ChapterList
@@ -70,11 +68,13 @@ import tachiyomi.domain.chapter.service.missingChaptersCount
 import tachiyomi.domain.library.service.LibraryPreferences
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.source.model.StubSource
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.TwoPanelBox
 import tachiyomi.presentation.core.components.VerticalFastScroller
 import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
 import tachiyomi.presentation.core.components.material.PullRefresh
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrollingUp
 import java.text.DateFormat
@@ -349,7 +349,7 @@ private fun MangaScreenSmallImpl(
                         val isReading = remember(state.chapters) {
                             state.chapters.fastAny { it.chapter.read }
                         }
-                        Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start))
+                        Text(text = localize(if (isReading) MR.strings.action_resume else MR.strings.action_start))
                     },
                     icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
                     onClick = onContinueReading,
@@ -608,7 +608,7 @@ fun MangaScreenLargeImpl(
                                 state.chapters.fastAny { it.chapter.read }
                             }
                             Text(
-                                text = stringResource(if (isReading) R.string.action_resume else R.string.action_start),
+                                text = localize(if (isReading) MR.strings.action_resume else MR.strings.action_start),
                             )
                         },
                         icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
@@ -787,8 +787,8 @@ private fun LazyListScope.sharedChapterItems(
             is ChapterList.Item -> {
                 MangaChapterListItem(
                     title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
-                        stringResource(
-                            R.string.display_mode_chapter,
+                        localize(
+                            MR.strings.display_mode_chapter,
                             formatChapterNumber(item.chapter.chapterNumber),
                         )
                     } else {
@@ -806,8 +806,8 @@ private fun LazyListScope.sharedChapterItems(
                     readProgress = item.chapter.lastPageRead
                         .takeIf { !item.chapter.read && it > 0L }
                         ?.let {
-                            stringResource(
-                                R.string.chapter_progress,
+                            localize(
+                                MR.strings.chapter_progress,
                                 it + 1,
                             )
                         },

+ 7 - 6
app/src/main/java/eu/kanade/presentation/manga/components/ChapterDownloadIndicator.kt

@@ -29,13 +29,14 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.components.DropdownMenu
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.download.model.Download
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.IconButtonTokens
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 
 enum class ChapterDownloadAction {
@@ -98,7 +99,7 @@ private fun NotDownloadedIndicator(
     ) {
         Icon(
             painter = painterResource(R.drawable.ic_download_chapter_24dp),
-            contentDescription = stringResource(R.string.manga_download),
+            contentDescription = localize(MR.strings.manga_download),
             modifier = Modifier.size(IndicatorSize),
             tint = MaterialTheme.colorScheme.onSurfaceVariant,
         )
@@ -156,14 +157,14 @@ private fun DownloadingIndicator(
         }
         DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
             DropdownMenuItem(
-                text = { Text(text = stringResource(R.string.action_start_downloading_now)) },
+                text = { Text(text = localize(MR.strings.action_start_downloading_now)) },
                 onClick = {
                     onClick(ChapterDownloadAction.START_NOW)
                     isMenuExpanded = false
                 },
             )
             DropdownMenuItem(
-                text = { Text(text = stringResource(R.string.action_cancel)) },
+                text = { Text(text = localize(MR.strings.action_cancel)) },
                 onClick = {
                     onClick(ChapterDownloadAction.CANCEL)
                     isMenuExpanded = false
@@ -204,7 +205,7 @@ private fun DownloadedIndicator(
         )
         DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
             DropdownMenuItem(
-                text = { Text(text = stringResource(R.string.action_delete)) },
+                text = { Text(text = localize(MR.strings.action_delete)) },
                 onClick = {
                     onClick(ChapterDownloadAction.DELETE)
                     isMenuExpanded = false
@@ -232,7 +233,7 @@ private fun ErrorIndicator(
     ) {
         Icon(
             imageVector = Icons.Outlined.ErrorOutline,
-            contentDescription = stringResource(R.string.chapter_error),
+            contentDescription = localize(MR.strings.chapter_error),
             modifier = Modifier.size(IndicatorSize),
             tint = MaterialTheme.colorScheme.error,
         )

+ 6 - 6
app/src/main/java/eu/kanade/presentation/manga/components/ChapterHeader.kt

@@ -9,12 +9,12 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 
 @Composable
 fun ChapterHeader(
@@ -35,9 +35,9 @@ fun ChapterHeader(
     ) {
         Text(
             text = if (chapterCount == null) {
-                stringResource(R.string.chapters)
+                localize(MR.strings.chapters)
             } else {
-                pluralStringResource(id = R.plurals.manga_num_chapters, count = chapterCount, chapterCount)
+                localizePlural(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount)
             },
             style = MaterialTheme.typography.titleMedium,
             color = MaterialTheme.colorScheme.onBackground,
@@ -54,7 +54,7 @@ private fun MissingChaptersWarning(count: Int) {
     }
 
     Text(
-        text = pluralStringResource(id = R.plurals.missing_chapters, count = count, count),
+        text = localizePlural(MR.plurals.missing_chapters, count = count, count),
         maxLines = 1,
         overflow = TextOverflow.Ellipsis,
         style = MaterialTheme.typography.bodySmall,

+ 14 - 13
app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt

@@ -48,7 +48,6 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.LocalHapticFeedback
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.vectorResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
@@ -59,6 +58,8 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import kotlin.time.Duration.Companion.seconds
 
 @Composable
@@ -107,7 +108,7 @@ fun MangaBottomActionMenu(
             ) {
                 if (onBookmarkClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_bookmark),
+                        title = localize(MR.strings.action_bookmark),
                         icon = Icons.Outlined.BookmarkAdd,
                         toConfirm = confirm[0],
                         onLongClick = { onLongClickItem(0) },
@@ -116,7 +117,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onRemoveBookmarkClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_remove_bookmark),
+                        title = localize(MR.strings.action_remove_bookmark),
                         icon = Icons.Outlined.BookmarkRemove,
                         toConfirm = confirm[1],
                         onLongClick = { onLongClickItem(1) },
@@ -125,7 +126,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onMarkAsReadClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_mark_as_read),
+                        title = localize(MR.strings.action_mark_as_read),
                         icon = Icons.Outlined.DoneAll,
                         toConfirm = confirm[2],
                         onLongClick = { onLongClickItem(2) },
@@ -134,7 +135,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onMarkAsUnreadClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_mark_as_unread),
+                        title = localize(MR.strings.action_mark_as_unread),
                         icon = Icons.Outlined.RemoveDone,
                         toConfirm = confirm[3],
                         onLongClick = { onLongClickItem(3) },
@@ -143,7 +144,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onMarkPreviousAsReadClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_mark_previous_as_read),
+                        title = localize(MR.strings.action_mark_previous_as_read),
                         icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp),
                         toConfirm = confirm[4],
                         onLongClick = { onLongClickItem(4) },
@@ -152,7 +153,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onDownloadClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_download),
+                        title = localize(MR.strings.action_download),
                         icon = Icons.Outlined.Download,
                         toConfirm = confirm[5],
                         onLongClick = { onLongClickItem(5) },
@@ -161,7 +162,7 @@ fun MangaBottomActionMenu(
                 }
                 if (onDeleteClicked != null) {
                     Button(
-                        title = stringResource(R.string.action_delete),
+                        title = localize(MR.strings.action_delete),
                         icon = Icons.Outlined.Delete,
                         toConfirm = confirm[6],
                         onLongClick = { onLongClickItem(6) },
@@ -258,21 +259,21 @@ fun LibraryBottomActionMenu(
                     .padding(horizontal = 8.dp, vertical = 12.dp),
             ) {
                 Button(
-                    title = stringResource(R.string.action_move_category),
+                    title = localize(MR.strings.action_move_category),
                     icon = Icons.AutoMirrored.Outlined.Label,
                     toConfirm = confirm[0],
                     onLongClick = { onLongClickItem(0) },
                     onClick = onChangeCategoryClicked,
                 )
                 Button(
-                    title = stringResource(R.string.action_mark_as_read),
+                    title = localize(MR.strings.action_mark_as_read),
                     icon = Icons.Outlined.DoneAll,
                     toConfirm = confirm[1],
                     onLongClick = { onLongClickItem(1) },
                     onClick = onMarkAsReadClicked,
                 )
                 Button(
-                    title = stringResource(R.string.action_mark_as_unread),
+                    title = localize(MR.strings.action_mark_as_unread),
                     icon = Icons.Outlined.RemoveDone,
                     toConfirm = confirm[2],
                     onLongClick = { onLongClickItem(2) },
@@ -281,7 +282,7 @@ fun LibraryBottomActionMenu(
                 if (onDownloadClicked != null) {
                     var downloadExpanded by remember { mutableStateOf(false) }
                     Button(
-                        title = stringResource(R.string.action_download),
+                        title = localize(MR.strings.action_download),
                         icon = Icons.Outlined.Download,
                         toConfirm = confirm[3],
                         onLongClick = { onLongClickItem(3) },
@@ -296,7 +297,7 @@ fun LibraryBottomActionMenu(
                     }
                 }
                 Button(
-                    title = stringResource(R.string.action_delete),
+                    title = localize(MR.strings.action_delete),
                     icon = Icons.Outlined.Delete,
                     toConfirm = confirm[4],
                     onLongClick = { onLongClickItem(4) },

+ 5 - 5
app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt

@@ -42,23 +42,22 @@ import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.ViewConfiguration
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.download.model.Download
 import me.saket.swipe.SwipeableActionsBox
 import me.saket.swipe.rememberSwipeableActionsState
 import tachiyomi.domain.library.service.LibraryPreferences
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.ReadItemAlpha
 import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.selectedBackground
 import kotlin.math.absoluteValue
 
 @Composable
 fun MangaChapterListItem(
-    modifier: Modifier = Modifier,
     title: String,
     date: String?,
     readProgress: String?,
@@ -75,6 +74,7 @@ fun MangaChapterListItem(
     onClick: () -> Unit,
     onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
     onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     val haptic = LocalHapticFeedback.current
     val density = LocalDensity.current
@@ -143,7 +143,7 @@ fun MangaChapterListItem(
                         if (!read) {
                             Icon(
                                 imageVector = Icons.Filled.Circle,
-                                contentDescription = stringResource(R.string.unread),
+                                contentDescription = localize(MR.strings.unread),
                                 modifier = Modifier
                                     .height(8.dp)
                                     .padding(end = 4.dp),
@@ -153,7 +153,7 @@ fun MangaChapterListItem(
                         if (bookmark) {
                             Icon(
                                 imageVector = Icons.Filled.Bookmark,
-                                contentDescription = stringResource(R.string.action_filter_bookmarked),
+                                contentDescription = localize(MR.strings.action_filter_bookmarked),
                                 modifier = Modifier
                                     .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
                                 tint = MaterialTheme.colorScheme.primary,

+ 8 - 8
app/src/main/java/eu/kanade/presentation/manga/components/MangaCoverDialog.kt

@@ -32,7 +32,6 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
@@ -47,11 +46,12 @@ import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.components.DropdownMenu
 import eu.kanade.presentation.manga.EditCoverAction
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
 import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.clickableNoIndication
 
 @Composable
@@ -85,7 +85,7 @@ fun MangaCoverDialog(
                         IconButton(onClick = onDismissRequest) {
                             Icon(
                                 imageVector = Icons.Outlined.Close,
-                                contentDescription = stringResource(R.string.action_close),
+                                contentDescription = localize(MR.strings.action_close),
                             )
                         }
                     }
@@ -94,12 +94,12 @@ fun MangaCoverDialog(
                         AppBarActions(
                             actions = persistentListOf(
                                 AppBar.Action(
-                                    title = stringResource(R.string.action_share),
+                                    title = localize(MR.strings.action_share),
                                     icon = Icons.Outlined.Share,
                                     onClick = onShareClick,
                                 ),
                                 AppBar.Action(
-                                    title = stringResource(R.string.action_save),
+                                    title = localize(MR.strings.action_save),
                                     icon = Icons.Outlined.Save,
                                     onClick = onSaveClick,
                                 ),
@@ -119,7 +119,7 @@ fun MangaCoverDialog(
                                 ) {
                                     Icon(
                                         imageVector = Icons.Outlined.Edit,
-                                        contentDescription = stringResource(R.string.action_edit_cover),
+                                        contentDescription = localize(MR.strings.action_edit_cover),
                                     )
                                 }
                                 DropdownMenu(
@@ -128,14 +128,14 @@ fun MangaCoverDialog(
                                     offset = DpOffset(8.dp, 0.dp),
                                 ) {
                                     DropdownMenuItem(
-                                        text = { Text(text = stringResource(R.string.action_edit)) },
+                                        text = { Text(text = localize(MR.strings.action_edit)) },
                                         onClick = {
                                             onEditClick(EditCoverAction.EDIT)
                                             expanded = false
                                         },
                                     )
                                     DropdownMenuItem(
-                                        text = { Text(text = stringResource(R.string.action_delete)) },
+                                        text = { Text(text = localize(MR.strings.action_delete)) },
                                         onClick = {
                                             onEditClick(EditCoverAction.DELETE)
                                             expanded = false

+ 10 - 10
app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt

@@ -12,13 +12,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
 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.DpSize
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.toImmutableList
 import tachiyomi.domain.manga.interactor.FetchInterval
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.WheelTextPicker
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun DeleteChaptersDialog(
@@ -29,7 +29,7 @@ fun DeleteChaptersDialog(
         onDismissRequest = onDismissRequest,
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -39,14 +39,14 @@ fun DeleteChaptersDialog(
                     onConfirm()
                 },
             ) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
         title = {
-            Text(text = stringResource(R.string.are_you_sure))
+            Text(text = localize(MR.strings.are_you_sure))
         },
         text = {
-            Text(text = stringResource(R.string.confirm_delete_chapters))
+            Text(text = localize(MR.strings.confirm_delete_chapters))
         },
     )
 }
@@ -61,7 +61,7 @@ fun SetIntervalDialog(
 
     AlertDialog(
         onDismissRequest = onDismissRequest,
-        title = { Text(text = stringResource(R.string.manga_modify_calculated_interval_title)) },
+        title = { Text(text = localize(MR.strings.manga_modify_calculated_interval_title)) },
         text = {
             BoxWithConstraints(
                 modifier = Modifier.fillMaxWidth(),
@@ -71,7 +71,7 @@ fun SetIntervalDialog(
                 val items = (0..FetchInterval.MAX_INTERVAL)
                     .map {
                         if (it == 0) {
-                            stringResource(R.string.label_default)
+                            localize(MR.strings.label_default)
                         } else {
                             it.toString()
                         }
@@ -87,7 +87,7 @@ fun SetIntervalDialog(
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -95,7 +95,7 @@ fun SetIntervalDialog(
                 onValueChanged(selectedInterval)
                 onDismissRequest()
             }) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
     )

+ 26 - 25
app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt

@@ -66,8 +66,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
@@ -80,8 +78,11 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.source.model.SManga
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.TextButton
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.clickableNoIndication
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 import kotlin.math.absoluteValue
@@ -178,9 +179,9 @@ fun MangaActionRow(
     Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
         MangaActionButton(
             title = if (favorite) {
-                stringResource(R.string.in_library)
+                localize(MR.strings.in_library)
             } else {
-                stringResource(R.string.add_to_library)
+                localize(MR.strings.add_to_library)
             },
             icon = if (favorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
             color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
@@ -189,8 +190,8 @@ fun MangaActionRow(
         )
         if (onEditIntervalClicked != null && fetchInterval != null) {
             MangaActionButton(
-                title = pluralStringResource(
-                    id = R.plurals.day,
+                title = localizePlural(
+                    MR.plurals.day,
                     count = fetchInterval.absoluteValue,
                     fetchInterval.absoluteValue,
                 ),
@@ -202,9 +203,9 @@ fun MangaActionRow(
         if (onTrackingClicked != null) {
             MangaActionButton(
                 title = if (trackingCount == 0) {
-                    stringResource(R.string.manga_tracking_tab)
+                    localize(MR.strings.manga_tracking_tab)
                 } else {
-                    pluralStringResource(id = R.plurals.num_trackers, count = trackingCount, trackingCount)
+                    localizePlural(MR.plurals.num_trackers, count = trackingCount, trackingCount)
                 },
                 icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
                 color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
@@ -213,7 +214,7 @@ fun MangaActionRow(
         }
         if (onWebViewClicked != null) {
             MangaActionButton(
-                title = stringResource(R.string.action_web_view),
+                title = localize(MR.strings.action_web_view),
                 icon = Icons.Outlined.Public,
                 color = defaultActionButtonColor,
                 onClick = onWebViewClicked,
@@ -237,7 +238,7 @@ fun ExpandableMangaDescription(
             mutableStateOf(defaultExpandState)
         }
         val desc =
-            description.takeIf { !it.isNullOrBlank() } ?: stringResource(R.string.description_placeholder)
+            description.takeIf { !it.isNullOrBlank() } ?: localize(MR.strings.description_placeholder)
         val trimmedDescription = remember(desc) {
             desc
                 .replace(whitespaceLineRegex, "\n")
@@ -267,14 +268,14 @@ fun ExpandableMangaDescription(
                     onDismissRequest = { showMenu = false },
                 ) {
                     DropdownMenuItem(
-                        text = { Text(text = stringResource(R.string.action_search)) },
+                        text = { Text(text = localize(MR.strings.action_search)) },
                         onClick = {
                             onTagSearch(tagSelected)
                             showMenu = false
                         },
                     )
                     DropdownMenuItem(
-                        text = { Text(text = stringResource(R.string.action_copy_to_clipboard)) },
+                        text = { Text(text = localize(MR.strings.action_copy_to_clipboard)) },
                         onClick = {
                             onCopyTagToClipboard(tagSelected)
                             showMenu = false
@@ -341,7 +342,7 @@ private fun MangaAndSourceTitlesLarge(
         MangaCover.Book(
             modifier = Modifier.fillMaxWidth(0.65f),
             data = coverDataProvider(),
-            contentDescription = stringResource(R.string.manga_cover),
+            contentDescription = localize(MR.strings.manga_cover),
             onClick = onCoverClick,
         )
         Spacer(modifier = Modifier.height(16.dp))
@@ -383,7 +384,7 @@ private fun MangaAndSourceTitlesSmall(
                 .sizeIn(maxWidth = 100.dp)
                 .align(Alignment.Top),
             data = coverDataProvider(),
-            contentDescription = stringResource(R.string.manga_cover),
+            contentDescription = localize(MR.strings.manga_cover),
             onClick = onCoverClick,
         )
         Column(
@@ -415,7 +416,7 @@ private fun MangaContentInfo(
 ) {
     val context = LocalContext.current
     Text(
-        text = title.ifBlank { stringResource(R.string.unknown_title) },
+        text = title.ifBlank { localize(MR.strings.unknown_title) },
         style = MaterialTheme.typography.titleLarge,
         modifier = Modifier.clickableNoIndication(
             onLongClick = {
@@ -445,7 +446,7 @@ private fun MangaContentInfo(
         )
         Text(
             text = author?.takeIf { it.isNotBlank() }
-                ?: stringResource(R.string.unknown_author),
+                ?: localize(MR.strings.unknown_author),
             style = MaterialTheme.typography.titleSmall,
             modifier = Modifier
                 .clickableNoIndication(
@@ -511,13 +512,13 @@ private fun MangaContentInfo(
         ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
             Text(
                 text = when (status) {
-                    SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
-                    SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
-                    SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
-                    SManga.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
-                    SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
-                    SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
-                    else -> stringResource(R.string.unknown)
+                    SManga.ONGOING.toLong() -> localize(MR.strings.ongoing)
+                    SManga.COMPLETED.toLong() -> localize(MR.strings.completed)
+                    SManga.LICENSED.toLong() -> localize(MR.strings.licensed)
+                    SManga.PUBLISHING_FINISHED.toLong() -> localize(MR.strings.publishing_finished)
+                    SManga.CANCELLED.toLong() -> localize(MR.strings.cancelled)
+                    SManga.ON_HIATUS.toLong() -> localize(MR.strings.on_hiatus)
+                    else -> localize(MR.strings.unknown)
                 },
                 overflow = TextOverflow.Ellipsis,
                 maxLines = 1,
@@ -591,8 +592,8 @@ private fun MangaSummary(
                     val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
                     Icon(
                         painter = rememberAnimatedVectorPainter(image, !expanded),
-                        contentDescription = stringResource(
-                            if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand,
+                        contentDescription = localize(
+                            if (expanded) MR.strings.manga_info_collapse else MR.strings.manga_info_expand,
                         ),
                         tint = MaterialTheme.colorScheme.onBackground,
                         modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),

+ 10 - 10
app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt

@@ -21,7 +21,6 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.components.AppBar
@@ -29,8 +28,9 @@ import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.components.DownloadDropdownMenu
 import eu.kanade.presentation.components.UpIcon
 import eu.kanade.presentation.manga.DownloadAction
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.theme.active
 
 @Composable
@@ -75,12 +75,12 @@ fun MangaToolbar(
                     AppBarActions(
                         persistentListOf(
                             AppBar.Action(
-                                title = stringResource(R.string.action_select_all),
+                                title = localize(MR.strings.action_select_all),
                                 icon = Icons.Outlined.SelectAll,
                                 onClick = onSelectAll,
                             ),
                             AppBar.Action(
-                                title = stringResource(R.string.action_select_inverse),
+                                title = localize(MR.strings.action_select_inverse),
                                 icon = Icons.Outlined.FlipToBack,
                                 onClick = onInvertSelection,
                             ),
@@ -104,7 +104,7 @@ fun MangaToolbar(
                                 if (onClickDownload != null) {
                                     add(
                                         AppBar.Action(
-                                            title = stringResource(R.string.manga_download),
+                                            title = localize(MR.strings.manga_download),
                                             icon = Icons.Outlined.Download,
                                             onClick = { downloadExpanded = !downloadExpanded },
                                         ),
@@ -112,7 +112,7 @@ fun MangaToolbar(
                                 }
                                 add(
                                     AppBar.Action(
-                                        title = stringResource(R.string.action_filter),
+                                        title = localize(MR.strings.action_filter),
                                         icon = Icons.Outlined.FilterList,
                                         iconTint = filterTint,
                                         onClick = onClickFilter,
@@ -120,14 +120,14 @@ fun MangaToolbar(
                                 )
                                 add(
                                     AppBar.OverflowAction(
-                                        title = stringResource(R.string.action_webview_refresh),
+                                        title = localize(MR.strings.action_webview_refresh),
                                         onClick = onClickRefresh,
                                     ),
                                 )
                                 if (onClickEditCategory != null) {
                                     add(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_edit_categories),
+                                            title = localize(MR.strings.action_edit_categories),
                                             onClick = onClickEditCategory,
                                         ),
                                     )
@@ -135,7 +135,7 @@ fun MangaToolbar(
                                 if (onClickMigrate != null) {
                                     add(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_migrate),
+                                            title = localize(MR.strings.action_migrate),
                                             onClick = onClickMigrate,
                                         ),
                                     )
@@ -143,7 +143,7 @@ fun MangaToolbar(
                                 if (onClickShare != null) {
                                     add(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_share),
+                                            title = localize(MR.strings.action_share),
                                             onClick = onClickShare,
                                         ),
                                     )

+ 3 - 3
app/src/main/java/eu/kanade/presentation/manga/components/MissingChapterCountListItem.kt

@@ -10,11 +10,11 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.pluralStringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 
 @Composable
@@ -34,7 +34,7 @@ fun MissingChapterCountListItem(
     ) {
         HorizontalDivider(modifier = Modifier.weight(1f))
         Text(
-            text = pluralStringResource(id = R.plurals.missing_chapters, count = count, count),
+            text = localizePlural(MR.plurals.missing_chapters, count = count, count),
             style = MaterialTheme.typography.labelMedium,
         )
         HorizontalDivider(modifier = Modifier.weight(1f))

+ 8 - 8
app/src/main/java/eu/kanade/presentation/manga/components/ScanlatorFilterDialog.kt

@@ -27,12 +27,12 @@ import androidx.compose.runtime.toMutableStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.DialogProperties
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.TextButton
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrolledToStart
 
@@ -49,10 +49,10 @@ fun ScanlatorFilterDialog(
     val mutableExcludedScanlators = remember(excludedScanlators) { excludedScanlators.toMutableStateList() }
     AlertDialog(
         onDismissRequest = onDismissRequest,
-        title = { Text(text = stringResource(R.string.exclude_scanlators)) },
+        title = { Text(text = localize(MR.strings.exclude_scanlators)) },
         text = textFunc@{
             if (sortedAvailableScanlators.isEmpty()) {
-                Text(text = stringResource(R.string.no_scanlators_found))
+                Text(text = localize(MR.strings.no_scanlators_found))
                 return@textFunc
             }
             Box {
@@ -108,16 +108,16 @@ fun ScanlatorFilterDialog(
         confirmButton = {
             if (sortedAvailableScanlators.isEmpty()) {
                 TextButton(onClick = onDismissRequest) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
             } else {
                 FlowRow {
                     TextButton(onClick = mutableExcludedScanlators::clear) {
-                        Text(text = stringResource(R.string.action_reset))
+                        Text(text = localize(MR.strings.action_reset))
                     }
                     Spacer(modifier = Modifier.weight(1f))
                     TextButton(onClick = onDismissRequest) {
-                        Text(text = stringResource(R.string.action_cancel))
+                        Text(text = localize(MR.strings.action_cancel))
                     }
                     TextButton(
                         onClick = {
@@ -125,7 +125,7 @@ fun ScanlatorFilterDialog(
                             onDismissRequest()
                         },
                     ) {
-                        Text(text = stringResource(R.string.action_ok))
+                        Text(text = localize(MR.strings.action_ok))
                     }
                 }
             }

+ 20 - 21
app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt

@@ -13,9 +13,7 @@ import androidx.compose.material.icons.automirrored.outlined.HelpOutline
 import androidx.compose.material.icons.automirrored.outlined.Label
 import androidx.compose.material.icons.outlined.CloudOff
 import androidx.compose.material.icons.outlined.GetApp
-import androidx.compose.material.icons.outlined.HelpOutline
 import androidx.compose.material.icons.outlined.Info
-import androidx.compose.material.icons.outlined.Label
 import androidx.compose.material.icons.outlined.QueryStats
 import androidx.compose.material.icons.outlined.Settings
 import androidx.compose.material.icons.outlined.Storage
@@ -24,8 +22,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.vectorResource
 import eu.kanade.presentation.components.WarningBanner
 import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
@@ -33,8 +29,11 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.more.DownloadQueueState
 import tachiyomi.core.Constants
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 
 @Composable
 fun MoreScreen(
@@ -62,7 +61,7 @@ fun MoreScreen(
             ) {
                 if (isFDroid) {
                     WarningBanner(
-                        textRes = R.string.fdroid_warning,
+                        textRes = MR.strings.fdroid_warning,
                         modifier = Modifier.clickable {
                             uriHandler.openUri(
                                 "https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds",
@@ -81,8 +80,8 @@ fun MoreScreen(
             }
             item {
                 SwitchPreferenceWidget(
-                    title = stringResource(R.string.label_downloaded_only),
-                    subtitle = stringResource(R.string.downloaded_only_summary),
+                    title = localize(MR.strings.label_downloaded_only),
+                    subtitle = localize(MR.strings.downloaded_only_summary),
                     icon = Icons.Outlined.CloudOff,
                     checked = downloadedOnly,
                     onCheckedChanged = onDownloadedOnlyChange,
@@ -90,8 +89,8 @@ fun MoreScreen(
             }
             item {
                 SwitchPreferenceWidget(
-                    title = stringResource(R.string.pref_incognito_mode),
-                    subtitle = stringResource(R.string.pref_incognito_mode_summary),
+                    title = localize(MR.strings.pref_incognito_mode),
+                    subtitle = localize(MR.strings.pref_incognito_mode_summary),
                     icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
                     checked = incognitoMode,
                     onCheckedChanged = onIncognitoModeChange,
@@ -103,17 +102,17 @@ fun MoreScreen(
             item {
                 val downloadQueueState = downloadQueueStateProvider()
                 TextPreferenceWidget(
-                    title = stringResource(R.string.label_download_queue),
+                    title = localize(MR.strings.label_download_queue),
                     subtitle = when (downloadQueueState) {
                         DownloadQueueState.Stopped -> null
                         is DownloadQueueState.Paused -> {
                             val pending = downloadQueueState.pending
                             if (pending == 0) {
-                                stringResource(R.string.paused)
+                                localize(MR.strings.paused)
                             } else {
-                                "${stringResource(R.string.paused)} • ${
-                                    pluralStringResource(
-                                        id = R.plurals.download_queue_summary,
+                                "${localize(MR.strings.paused)} • ${
+                                    localizePlural(
+                                        MR.plurals.download_queue_summary,
                                         count = pending,
                                         pending,
                                     )
@@ -122,7 +121,7 @@ fun MoreScreen(
                         }
                         is DownloadQueueState.Downloading -> {
                             val pending = downloadQueueState.pending
-                            pluralStringResource(id = R.plurals.download_queue_summary, count = pending, pending)
+                            localizePlural(MR.plurals.download_queue_summary, count = pending, pending)
                         }
                     },
                     icon = Icons.Outlined.GetApp,
@@ -131,21 +130,21 @@ fun MoreScreen(
             }
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.categories),
+                    title = localize(MR.strings.categories),
                     icon = Icons.AutoMirrored.Outlined.Label,
                     onPreferenceClick = onClickCategories,
                 )
             }
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.label_stats),
+                    title = localize(MR.strings.label_stats),
                     icon = Icons.Outlined.QueryStats,
                     onPreferenceClick = onClickStats,
                 )
             }
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.label_data_storage),
+                    title = localize(MR.strings.label_data_storage),
                     icon = Icons.Outlined.Storage,
                     onPreferenceClick = onClickDataAndStorage,
                 )
@@ -155,21 +154,21 @@ fun MoreScreen(
 
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.label_settings),
+                    title = localize(MR.strings.label_settings),
                     icon = Icons.Outlined.Settings,
                     onPreferenceClick = onClickSettings,
                 )
             }
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.pref_category_about),
+                    title = localize(MR.strings.pref_category_about),
                     icon = Icons.Outlined.Info,
                     onPreferenceClick = onClickAbout,
                 )
             }
             item {
                 TextPreferenceWidget(
-                    title = stringResource(R.string.label_help),
+                    title = localize(MR.strings.label_help),
                     icon = Icons.AutoMirrored.Outlined.HelpOutline,
                     onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
                 )

+ 6 - 6
app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt

@@ -14,7 +14,6 @@ import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import com.halilibo.richtext.markdown.Markdown
@@ -22,8 +21,9 @@ import com.halilibo.richtext.ui.RichTextStyle
 import com.halilibo.richtext.ui.material3.Material3RichText
 import com.halilibo.richtext.ui.string.RichTextStringStyle
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.InfoScreen
 
 @Composable
@@ -36,11 +36,11 @@ fun NewUpdateScreen(
 ) {
     InfoScreen(
         icon = Icons.Outlined.NewReleases,
-        headingText = stringResource(R.string.update_check_notification_update_available),
+        headingText = localize(MR.strings.update_check_notification_update_available),
         subtitleText = versionName,
-        acceptText = stringResource(R.string.update_check_confirm),
+        acceptText = localize(MR.strings.update_check_confirm),
         onAcceptClick = onAcceptUpdate,
-        rejectText = stringResource(R.string.action_not_now),
+        rejectText = localize(MR.strings.action_not_now),
         onRejectClick = onRejectUpdate,
     ) {
         Material3RichText(
@@ -59,7 +59,7 @@ fun NewUpdateScreen(
                 onClick = onOpenInBrowser,
                 modifier = Modifier.padding(top = MaterialTheme.padding.small),
             ) {
-                Text(text = stringResource(R.string.update_check_open))
+                Text(text = localize(MR.strings.update_check_open))
                 Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
                 Icon(imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null)
             }

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt

@@ -3,9 +3,9 @@ package eu.kanade.presentation.more.settings
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.Tracker
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.core.preference.Preference as PreferenceData
 
 sealed class Preference {
@@ -109,7 +109,7 @@ sealed class Preference {
                     v.map { e[it] }
                         .takeIf { it.isNotEmpty() }
                         ?.joinToString()
-                } ?: stringResource(R.string.none)
+                } ?: localize(MR.strings.none)
                 subtitle?.format(combined)
             },
             override val icon: ImageVector? = null,

+ 4 - 4
app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScaffold.kt

@@ -1,15 +1,15 @@
 package eu.kanade.presentation.more.settings
 
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.components.AppBar
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun PreferenceScaffold(
-    @StringRes titleRes: Int,
+    titleRes: StringResource,
     actions: @Composable RowScope.() -> Unit = {},
     onBackPressed: (() -> Unit)? = null,
     itemsProvider: @Composable () -> List<Preference>,
@@ -17,7 +17,7 @@ fun PreferenceScaffold(
     Scaffold(
         topBar = {
             AppBar(
-                title = stringResource(titleRes),
+                title = localize(titleRes),
                 navigateUp = onBackPressed,
                 actions = actions,
                 scrollBehavior = it,

+ 9 - 9
app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt

@@ -3,10 +3,10 @@ package eu.kanade.presentation.more.settings.screen
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.category.visualName
-import eu.kanade.tachiyomi.R
 import tachiyomi.domain.category.model.Category
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 /**
  * Returns a string of categories name for settings subtitle
@@ -34,15 +34,15 @@ fun getCategoriesLabel(
         includedCategories.isNotEmpty() && includedCategories.size != allCategories.size ->
             includedCategories.joinToString { it.visualName(context) }
         // All explicitly selected
-        includedCategories.size == allCategories.size -> stringResource(R.string.all)
-        allExcluded -> stringResource(R.string.none)
-        else -> stringResource(R.string.all)
+        includedCategories.size == allCategories.size -> localize(MR.strings.all)
+        allExcluded -> localize(MR.strings.none)
+        else -> localize(MR.strings.all)
     }
     val excludedItemsText = when {
-        excludedCategories.isEmpty() -> stringResource(R.string.none)
-        allExcluded -> stringResource(R.string.all)
+        excludedCategories.isEmpty() -> localize(MR.strings.none)
+        allExcluded -> localize(MR.strings.all)
         else -> excludedCategories.joinToString { it.visualName(context) }
     }
-    return stringResource(R.string.include, includedItemsText) + "\n" +
-        stringResource(R.string.exclude, excludedItemsText)
+    return localize(MR.strings.include, includedItemsText) + "\n" +
+        localize(MR.strings.exclude, excludedItemsText)
 }

+ 2 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/SearchableSettings.kt

@@ -1,10 +1,10 @@
 package eu.kanade.presentation.more.settings.screen
 
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import cafe.adriel.voyager.core.screen.Screen
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.PreferenceScaffold
 import eu.kanade.presentation.util.LocalBackPress
@@ -13,8 +13,7 @@ interface SearchableSettings : Screen {
 
     @Composable
     @ReadOnlyComposable
-    @StringRes
-    fun getTitleRes(): Int
+    fun getTitleRes(): StringResource
 
     @Composable
     fun getPreferences(): List<Preference>

+ 52 - 54
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt

@@ -7,7 +7,6 @@ import android.os.Build
 import android.provider.Settings
 import android.webkit.WebStorage
 import android.webkit.WebView
-import androidx.annotation.StringRes
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
@@ -21,7 +20,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
 import androidx.core.net.toUri
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
@@ -29,9 +27,8 @@ import eu.kanade.domain.base.BasePreferences
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
 import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.download.DownloadCache
-import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
+import eu.kanade.tachiyomi.data.library.MetadataUpdateJob
 import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.network.NetworkPreferences
 import eu.kanade.tachiyomi.network.PREF_DOH_360
@@ -60,6 +57,8 @@ import tachiyomi.core.util.lang.launchNonCancellable
 import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.manga.interactor.ResetViewerFlags
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -69,8 +68,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_advanced
+    override fun getTitleRes() = MR.strings.pref_category_advanced
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -86,13 +84,13 @@ object SettingsAdvancedScreen : SearchableSettings {
                 listOf(
                     Preference.PreferenceItem.SwitchPreference(
                         pref = basePreferences.acraEnabled(),
-                        title = stringResource(R.string.pref_enable_acra),
-                        subtitle = stringResource(R.string.pref_acra_summary),
+                        title = localize(MR.strings.pref_enable_acra),
+                        subtitle = localize(MR.strings.pref_acra_summary),
                         enabled = isPreviewBuildType || isReleaseBuildType,
                     ),
                     Preference.PreferenceItem.TextPreference(
-                        title = stringResource(R.string.pref_dump_crash_logs),
-                        subtitle = stringResource(R.string.pref_dump_crash_logs_summary),
+                        title = localize(MR.strings.pref_dump_crash_logs),
+                        subtitle = localize(MR.strings.pref_dump_crash_logs_summary),
                         onClick = {
                             scope.launch {
                                 CrashLogUtil(context).dumpLogs()
@@ -101,15 +99,15 @@ object SettingsAdvancedScreen : SearchableSettings {
                     ),
                     Preference.PreferenceItem.SwitchPreference(
                         pref = networkPreferences.verboseLogging(),
-                        title = stringResource(R.string.pref_verbose_logging),
-                        subtitle = stringResource(R.string.pref_verbose_logging_summary),
+                        title = localize(MR.strings.pref_verbose_logging),
+                        subtitle = localize(MR.strings.pref_verbose_logging_summary),
                         onValueChanged = {
-                            context.toast(R.string.requires_app_restart)
+                            context.toast(MR.strings.requires_app_restart)
                             true
                         },
                     ),
                     Preference.PreferenceItem.TextPreference(
-                        title = stringResource(R.string.pref_debug_info),
+                        title = localize(MR.strings.pref_debug_info),
                         onClick = { navigator.push(DebugInfoScreen()) },
                     ),
                 ),
@@ -117,7 +115,7 @@ object SettingsAdvancedScreen : SearchableSettings {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                 add(
                     Preference.PreferenceItem.TextPreference(
-                        title = stringResource(R.string.pref_manage_notifications),
+                        title = localize(MR.strings.pref_manage_notifications),
                         onClick = {
                             val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
                                 putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
@@ -145,11 +143,11 @@ object SettingsAdvancedScreen : SearchableSettings {
         val uriHandler = LocalUriHandler.current
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_background_activity),
+            title = localize(MR.strings.label_background_activity),
             preferenceItems = listOf(
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_disable_battery_optimization),
-                    subtitle = stringResource(R.string.pref_disable_battery_optimization_summary),
+                    title = localize(MR.strings.pref_disable_battery_optimization),
+                    subtitle = localize(MR.strings.pref_disable_battery_optimization_summary),
                     onClick = {
                         val packageName: String = context.packageName
                         if (!context.powerManager.isIgnoringBatteryOptimizations(packageName)) {
@@ -162,16 +160,16 @@ object SettingsAdvancedScreen : SearchableSettings {
                                 }
                                 context.startActivity(intent)
                             } catch (e: ActivityNotFoundException) {
-                                context.toast(R.string.battery_optimization_setting_activity_not_found)
+                                context.toast(MR.strings.battery_optimization_setting_activity_not_found)
                             }
                         } else {
-                            context.toast(R.string.battery_optimization_disabled)
+                            context.toast(MR.strings.battery_optimization_disabled)
                         }
                     },
                 ),
                 Preference.PreferenceItem.TextPreference(
                     title = "Don't kill my app!",
-                    subtitle = stringResource(R.string.about_dont_kill_my_app),
+                    subtitle = localize(MR.strings.about_dont_kill_my_app),
                     onClick = { uriHandler.openUri("https://dontkillmyapp.com/") },
                 ),
             ),
@@ -184,19 +182,19 @@ object SettingsAdvancedScreen : SearchableSettings {
         val navigator = LocalNavigator.currentOrThrow
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_data),
+            title = localize(MR.strings.label_data),
             preferenceItems = listOf(
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_invalidate_download_cache),
-                    subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
+                    title = localize(MR.strings.pref_invalidate_download_cache),
+                    subtitle = localize(MR.strings.pref_invalidate_download_cache_summary),
                     onClick = {
                         Injekt.get<DownloadCache>().invalidateCache()
-                        context.toast(R.string.download_cache_invalidated)
+                        context.toast(MR.strings.download_cache_invalidated)
                     },
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_clear_database),
-                    subtitle = stringResource(R.string.pref_clear_database_summary),
+                    title = localize(MR.strings.pref_clear_database),
+                    subtitle = localize(MR.strings.pref_clear_database_summary),
                     onClick = { navigator.push(ClearDatabaseScreen()) },
                 ),
             ),
@@ -214,17 +212,17 @@ object SettingsAdvancedScreen : SearchableSettings {
         val userAgent by userAgentPref.collectAsState()
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_network),
+            title = localize(MR.strings.label_network),
             preferenceItems = listOf(
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_clear_cookies),
+                    title = localize(MR.strings.pref_clear_cookies),
                     onClick = {
                         networkHelper.cookieJar.removeAll()
-                        context.toast(R.string.cookies_cleared)
+                        context.toast(MR.strings.cookies_cleared)
                     },
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_clear_webview_data),
+                    title = localize(MR.strings.pref_clear_webview_data),
                     onClick = {
                         try {
                             WebView(context).run {
@@ -236,18 +234,18 @@ object SettingsAdvancedScreen : SearchableSettings {
                             }
                             WebStorage.getInstance().deleteAllData()
                             context.applicationInfo?.dataDir?.let { File("$it/app_webview/").deleteRecursively() }
-                            context.toast(R.string.webview_data_deleted)
+                            context.toast(MR.strings.webview_data_deleted)
                         } catch (e: Throwable) {
                             logcat(LogPriority.ERROR, e)
-                            context.toast(R.string.cache_delete_error)
+                            context.toast(MR.strings.cache_delete_error)
                         }
                     },
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = networkPreferences.dohProvider(),
-                    title = stringResource(R.string.pref_dns_over_https),
+                    title = localize(MR.strings.pref_dns_over_https),
                     entries = mapOf(
-                        -1 to stringResource(R.string.disabled),
+                        -1 to localize(MR.strings.disabled),
                         PREF_DOH_CLOUDFLARE to "Cloudflare",
                         PREF_DOH_GOOGLE to "Google",
                         PREF_DOH_ADGUARD to "AdGuard",
@@ -262,30 +260,30 @@ object SettingsAdvancedScreen : SearchableSettings {
                         PREF_DOH_SHECAN to "Shecan",
                     ),
                     onValueChanged = {
-                        context.toast(R.string.requires_app_restart)
+                        context.toast(MR.strings.requires_app_restart)
                         true
                     },
                 ),
                 Preference.PreferenceItem.EditTextPreference(
                     pref = userAgentPref,
-                    title = stringResource(R.string.pref_user_agent_string),
+                    title = localize(MR.strings.pref_user_agent_string),
                     onValueChanged = {
                         try {
                             // OkHttp checks for valid values internally
                             Headers.Builder().add("User-Agent", it)
                         } catch (_: IllegalArgumentException) {
-                            context.toast(R.string.error_user_agent_string_invalid)
+                            context.toast(MR.strings.error_user_agent_string_invalid)
                             return@EditTextPreference false
                         }
                         true
                     },
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_reset_user_agent_string),
+                    title = localize(MR.strings.pref_reset_user_agent_string),
                     enabled = remember(userAgent) { userAgent != userAgentPref.defaultValue() },
                     onClick = {
                         userAgentPref.delete()
-                        context.toast(R.string.requires_app_restart)
+                        context.toast(MR.strings.requires_app_restart)
                     },
                 ),
             ),
@@ -298,23 +296,23 @@ object SettingsAdvancedScreen : SearchableSettings {
         val context = LocalContext.current
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_library),
+            title = localize(MR.strings.label_library),
             preferenceItems = listOf(
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_refresh_library_covers),
-                    onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.COVERS) },
+                    title = localize(MR.strings.pref_refresh_library_covers),
+                    onClick = { MetadataUpdateJob.startNow(context) },
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_reset_viewer_flags),
-                    subtitle = stringResource(R.string.pref_reset_viewer_flags_summary),
+                    title = localize(MR.strings.pref_reset_viewer_flags),
+                    subtitle = localize(MR.strings.pref_reset_viewer_flags_summary),
                     onClick = {
                         scope.launchNonCancellable {
                             val success = Injekt.get<ResetViewerFlags>().await()
                             withUIContext {
                                 val message = if (success) {
-                                    R.string.pref_reset_viewer_flags_success
+                                    MR.strings.pref_reset_viewer_flags_success
                                 } else {
-                                    R.string.pref_reset_viewer_flags_error
+                                    MR.strings.pref_reset_viewer_flags_error
                                 }
                                 context.toast(message)
                             }
@@ -338,11 +336,11 @@ object SettingsAdvancedScreen : SearchableSettings {
             val dismiss = { shizukuMissing = false }
             AlertDialog(
                 onDismissRequest = dismiss,
-                title = { Text(text = stringResource(R.string.ext_installer_shizuku)) },
-                text = { Text(text = stringResource(R.string.ext_installer_shizuku_unavailable_dialog)) },
+                title = { Text(text = localize(MR.strings.ext_installer_shizuku)) },
+                text = { Text(text = localize(MR.strings.ext_installer_shizuku_unavailable_dialog)) },
                 dismissButton = {
                     TextButton(onClick = dismiss) {
-                        Text(text = stringResource(R.string.action_cancel))
+                        Text(text = localize(MR.strings.action_cancel))
                     }
                 },
                 confirmButton = {
@@ -352,19 +350,19 @@ object SettingsAdvancedScreen : SearchableSettings {
                             uriHandler.openUri("https://shizuku.rikka.app/download")
                         },
                     ) {
-                        Text(text = stringResource(R.string.action_ok))
+                        Text(text = localize(MR.strings.action_ok))
                     }
                 },
             )
         }
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_extensions),
+            title = localize(MR.strings.label_extensions),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = extensionInstallerPref,
-                    title = stringResource(R.string.ext_installer_pref),
+                    title = localize(MR.strings.ext_installer_pref),
                     entries = extensionInstallerPref.entries
-                        .associateWith { stringResource(it.titleResId) },
+                        .associateWith { localize(it.titleRes) },
                     onValueChanged = {
                         if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
                             !context.isShizukuInstalled

+ 25 - 25
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt

@@ -3,7 +3,6 @@ package eu.kanade.presentation.more.settings.screen
 import android.app.Activity
 import android.content.Context
 import android.os.Build
-import androidx.annotation.StringRes
 import androidx.appcompat.app.AppCompatDelegate
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -13,7 +12,6 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.core.app.ActivityCompat
 import androidx.core.os.LocaleListCompat
 import eu.kanade.domain.ui.UiPreferences
@@ -29,6 +27,9 @@ import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.merge
 import org.xmlpull.v1.XmlPullParser
+import tachiyomi.core.i18n.localize
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -38,8 +39,7 @@ object SettingsAppearanceScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_appearance
+    override fun getTitleRes() = MR.strings.pref_category_appearance
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -76,26 +76,26 @@ object SettingsAppearanceScreen : SearchableSettings {
         }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_theme),
+            title = localize(MR.strings.pref_category_theme),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = themeModePref,
-                    title = stringResource(R.string.pref_theme_mode),
+                    title = localize(MR.strings.pref_theme_mode),
                     entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                         mapOf(
-                            ThemeMode.SYSTEM to stringResource(R.string.theme_system),
-                            ThemeMode.LIGHT to stringResource(R.string.theme_light),
-                            ThemeMode.DARK to stringResource(R.string.theme_dark),
+                            ThemeMode.SYSTEM to localize(MR.strings.theme_system),
+                            ThemeMode.LIGHT to localize(MR.strings.theme_light),
+                            ThemeMode.DARK to localize(MR.strings.theme_dark),
                         )
                     } else {
                         mapOf(
-                            ThemeMode.LIGHT to stringResource(R.string.theme_light),
-                            ThemeMode.DARK to stringResource(R.string.theme_dark),
+                            ThemeMode.LIGHT to localize(MR.strings.theme_light),
+                            ThemeMode.DARK to localize(MR.strings.theme_dark),
                         )
                     },
                 ),
                 Preference.PreferenceItem.CustomPreference(
-                    title = stringResource(R.string.pref_app_theme),
+                    title = localize(MR.strings.pref_app_theme),
                 ) { item ->
                     val value by appThemePref.collectAsState()
                     AppThemePreferenceWidget(
@@ -107,7 +107,7 @@ object SettingsAppearanceScreen : SearchableSettings {
                 },
                 Preference.PreferenceItem.SwitchPreference(
                     pref = amoledPref,
-                    title = stringResource(R.string.pref_dark_theme_pure_black),
+                    title = localize(MR.strings.pref_dark_theme_pure_black),
                     enabled = themeMode != ThemeMode.LIGHT,
                 ),
             ),
@@ -140,11 +140,11 @@ object SettingsAppearanceScreen : SearchableSettings {
         }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_display),
+            title = localize(MR.strings.pref_category_display),
             preferenceItems = listOf(
                 Preference.PreferenceItem.BasicListPreference(
                     value = currentLanguage,
-                    title = stringResource(R.string.pref_app_language),
+                    title = localize(MR.strings.pref_app_language),
                     entries = langs,
                     onValueChanged = { newValue ->
                         currentLanguage = newValue
@@ -153,28 +153,28 @@ object SettingsAppearanceScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = uiPreferences.tabletUiMode(),
-                    title = stringResource(R.string.pref_tablet_ui_mode),
-                    entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
+                    title = localize(MR.strings.pref_tablet_ui_mode),
+                    entries = TabletUiMode.entries.associateWith { localize(it.titleRes) },
                     onValueChanged = {
-                        context.toast(R.string.requires_app_restart)
+                        context.toast(MR.strings.requires_app_restart)
                         true
                     },
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = uiPreferences.dateFormat(),
-                    title = stringResource(R.string.pref_date_format),
+                    title = localize(MR.strings.pref_date_format),
                     entries = DateFormats
                         .associateWith {
                             val formattedDate = UiPreferences.dateFormat(it).format(now)
-                            "${it.ifEmpty { stringResource(R.string.label_default) }} ($formattedDate)"
+                            "${it.ifEmpty { localize(MR.strings.label_default) }} ($formattedDate)"
                         },
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = uiPreferences.relativeTime(),
-                    title = stringResource(R.string.pref_relative_format),
-                    subtitle = stringResource(
-                        R.string.pref_relative_format_summary,
-                        stringResource(R.string.relative_time_today),
+                    title = localize(MR.strings.pref_relative_format),
+                    subtitle = localize(
+                        MR.strings.pref_relative_format_summary,
+                        localize(MR.strings.relative_time_today),
                         formattedNow,
                     ),
                 ),
@@ -201,7 +201,7 @@ object SettingsAppearanceScreen : SearchableSettings {
         }
 
         langs.sortBy { it.second }
-        langs.add(0, Pair("", context.getString(R.string.label_default)))
+        langs.add(0, Pair("", context.localize(MR.strings.label_default)))
 
         return langs.toMap()
     }

+ 11 - 12
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt

@@ -1,16 +1,16 @@
 package eu.kanade.presentation.more.settings.screen
 
-import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.fragment.app.FragmentActivity
 import eu.kanade.domain.source.service.SourcePreferences
 import eu.kanade.presentation.more.settings.Preference
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
+import tachiyomi.core.i18n.localize
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 
@@ -18,8 +18,7 @@ object SettingsBrowseScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.browse
+    override fun getTitleRes() = MR.strings.browse
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -27,28 +26,28 @@ object SettingsBrowseScreen : SearchableSettings {
         val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
         return listOf(
             Preference.PreferenceGroup(
-                title = stringResource(R.string.label_sources),
+                title = localize(MR.strings.label_sources),
                 preferenceItems = listOf(
                     Preference.PreferenceItem.SwitchPreference(
                         pref = sourcePreferences.hideInLibraryItems(),
-                        title = stringResource(R.string.pref_hide_in_library_items),
+                        title = localize(MR.strings.pref_hide_in_library_items),
                     ),
                 ),
             ),
             Preference.PreferenceGroup(
-                title = stringResource(R.string.pref_category_nsfw_content),
+                title = localize(MR.strings.pref_category_nsfw_content),
                 preferenceItems = listOf(
                     Preference.PreferenceItem.SwitchPreference(
                         pref = sourcePreferences.showNsfwSource(),
-                        title = stringResource(R.string.pref_show_nsfw_source),
-                        subtitle = stringResource(R.string.requires_app_restart),
+                        title = localize(MR.strings.pref_show_nsfw_source),
+                        subtitle = localize(MR.strings.requires_app_restart),
                         onValueChanged = {
                             (context as FragmentActivity).authenticate(
-                                title = context.getString(R.string.pref_category_nsfw_content),
+                                title = context.localize(MR.strings.pref_category_nsfw_content),
                             )
                         },
                     ),
-                    Preference.PreferenceItem.InfoPreference(stringResource(R.string.parental_controls_info)),
+                    Preference.PreferenceItem.InfoPreference(localize(MR.strings.parental_controls_info)),
                 ),
             ),
         )

+ 39 - 40
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt

@@ -8,7 +8,6 @@ import android.text.format.Formatter
 import android.widget.Toast
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
@@ -27,7 +26,6 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
 import eu.kanade.presentation.more.settings.Preference
@@ -36,7 +34,6 @@ import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
 import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
 import eu.kanade.presentation.permissions.PermissionRequestHelper
 import eu.kanade.presentation.util.relativeTimeSpanString
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.BackupCreateJob
 import eu.kanade.tachiyomi.data.backup.BackupFileValidator
 import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
@@ -46,11 +43,14 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import eu.kanade.tachiyomi.util.system.toast
 import logcat.LogPriority
+import tachiyomi.core.i18n.localize
 import tachiyomi.core.util.lang.launchNonCancellable
 import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.backup.service.BackupPreferences
 import tachiyomi.domain.library.service.LibraryPreferences
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -59,8 +59,7 @@ object SettingsDataScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.label_data_storage
+    override fun getTitleRes() = MR.strings.label_data_storage
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -82,7 +81,7 @@ object SettingsDataScreen : SearchableSettings {
         val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_backup),
+            title = localize(MR.strings.label_backup),
             preferenceItems = listOf(
                 // Manual actions
                 getCreateBackupPref(),
@@ -91,14 +90,14 @@ object SettingsDataScreen : SearchableSettings {
                 // Automatic backups
                 Preference.PreferenceItem.ListPreference(
                     pref = backupIntervalPref,
-                    title = stringResource(R.string.pref_backup_interval),
+                    title = localize(MR.strings.pref_backup_interval),
                     entries = mapOf(
-                        0 to stringResource(R.string.off),
-                        6 to stringResource(R.string.update_6hour),
-                        12 to stringResource(R.string.update_12hour),
-                        24 to stringResource(R.string.update_24hour),
-                        48 to stringResource(R.string.update_48hour),
-                        168 to stringResource(R.string.update_weekly),
+                        0 to localize(MR.strings.off),
+                        6 to localize(MR.strings.update_6hour),
+                        12 to localize(MR.strings.update_12hour),
+                        24 to localize(MR.strings.update_24hour),
+                        48 to localize(MR.strings.update_48hour),
+                        168 to localize(MR.strings.update_weekly),
                     ),
                     onValueChanged = {
                         BackupCreateJob.setupTask(context, it)
@@ -108,12 +107,12 @@ object SettingsDataScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = backupPreferences.numberOfBackups(),
                     enabled = backupInterval != 0,
-                    title = stringResource(R.string.pref_backup_slots),
+                    title = localize(MR.strings.pref_backup_slots),
                     entries = listOf(2, 3, 4, 5).associateWith { it.toString() },
                 ),
                 Preference.PreferenceItem.InfoPreference(
-                    stringResource(R.string.backup_info) + "\n\n" +
-                        stringResource(R.string.last_auto_backup_info, relativeTimeSpanString(lastAutoBackup)),
+                    localize(MR.strings.backup_info) + "\n\n" +
+                        localize(MR.strings.last_auto_backup_info, relativeTimeSpanString(lastAutoBackup)),
                 ),
             ),
         )
@@ -123,8 +122,8 @@ object SettingsDataScreen : SearchableSettings {
     private fun getCreateBackupPref(): Preference.PreferenceItem.TextPreference {
         val navigator = LocalNavigator.currentOrThrow
         return Preference.PreferenceItem.TextPreference(
-            title = stringResource(R.string.pref_create_backup),
-            subtitle = stringResource(R.string.pref_create_backup_summ),
+            title = localize(MR.strings.pref_create_backup),
+            subtitle = localize(MR.strings.pref_create_backup_summ),
             onClick = { navigator.push(CreateBackupScreen()) },
         )
     }
@@ -139,7 +138,7 @@ object SettingsDataScreen : SearchableSettings {
                 is InvalidRestore -> {
                     AlertDialog(
                         onDismissRequest = onDismissRequest,
-                        title = { Text(text = stringResource(R.string.invalid_backup_file)) },
+                        title = { Text(text = localize(MR.strings.invalid_backup_file)) },
                         text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
                         dismissButton = {
                             TextButton(
@@ -148,12 +147,12 @@ object SettingsDataScreen : SearchableSettings {
                                     onDismissRequest()
                                 },
                             ) {
-                                Text(text = stringResource(R.string.action_copy_to_clipboard))
+                                Text(text = localize(MR.strings.action_copy_to_clipboard))
                             }
                         },
                         confirmButton = {
                             TextButton(onClick = onDismissRequest) {
-                                Text(text = stringResource(R.string.action_ok))
+                                Text(text = localize(MR.strings.action_ok))
                             }
                         },
                     )
@@ -161,15 +160,15 @@ object SettingsDataScreen : SearchableSettings {
                 is MissingRestoreComponents -> {
                     AlertDialog(
                         onDismissRequest = onDismissRequest,
-                        title = { Text(text = stringResource(R.string.pref_restore_backup)) },
+                        title = { Text(text = localize(MR.strings.pref_restore_backup)) },
                         text = {
                             Column(
                                 modifier = Modifier.verticalScroll(rememberScrollState()),
                             ) {
                                 val msg = buildString {
-                                    append(stringResource(R.string.backup_restore_content_full))
+                                    append(localize(MR.strings.backup_restore_content_full))
                                     if (err.sources.isNotEmpty()) {
-                                        append("\n\n").append(stringResource(R.string.backup_restore_missing_sources))
+                                        append("\n\n").append(localize(MR.strings.backup_restore_missing_sources))
                                         err.sources.joinTo(
                                             this,
                                             separator = "\n- ",
@@ -177,7 +176,7 @@ object SettingsDataScreen : SearchableSettings {
                                         )
                                     }
                                     if (err.trackers.isNotEmpty()) {
-                                        append("\n\n").append(stringResource(R.string.backup_restore_missing_trackers))
+                                        append("\n\n").append(localize(MR.strings.backup_restore_missing_trackers))
                                         err.trackers.joinTo(
                                             this,
                                             separator = "\n- ",
@@ -195,7 +194,7 @@ object SettingsDataScreen : SearchableSettings {
                                     onDismissRequest()
                                 },
                             ) {
-                                Text(text = stringResource(R.string.action_restore))
+                                Text(text = localize(MR.strings.action_restore))
                             }
                         },
                     )
@@ -208,12 +207,12 @@ object SettingsDataScreen : SearchableSettings {
             object : ActivityResultContracts.GetContent() {
                 override fun createIntent(context: Context, input: String): Intent {
                     val intent = super.createIntent(context, input)
-                    return Intent.createChooser(intent, context.getString(R.string.file_select_backup))
+                    return Intent.createChooser(intent, context.localize(MR.strings.file_select_backup))
                 }
             },
         ) {
             if (it == null) {
-                context.toast(R.string.file_null_uri_error)
+                context.toast(MR.strings.file_null_uri_error)
                 return@rememberLauncherForActivityResult
             }
 
@@ -233,17 +232,17 @@ object SettingsDataScreen : SearchableSettings {
         }
 
         return Preference.PreferenceItem.TextPreference(
-            title = stringResource(R.string.pref_restore_backup),
-            subtitle = stringResource(R.string.pref_restore_backup_summ),
+            title = localize(MR.strings.pref_restore_backup),
+            subtitle = localize(MR.strings.pref_restore_backup_summ),
             onClick = {
                 if (!BackupRestoreJob.isRunning(context)) {
                     if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
-                        context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
+                        context.toast(MR.strings.restore_miui_warning, Toast.LENGTH_LONG)
                     }
                     // no need to catch because it's wrapped with a chooser
                     chooseBackup.launch("*/*")
                 } else {
-                    context.toast(R.string.restore_in_progress)
+                    context.toast(MR.strings.restore_in_progress)
                 }
             },
         )
@@ -260,31 +259,31 @@ object SettingsDataScreen : SearchableSettings {
         val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.label_data),
+            title = localize(MR.strings.label_data),
             preferenceItems = listOf(
                 getStorageInfoPref(cacheReadableSize),
 
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.pref_clear_chapter_cache),
-                    subtitle = stringResource(R.string.used_cache, cacheReadableSize),
+                    title = localize(MR.strings.pref_clear_chapter_cache),
+                    subtitle = localize(MR.strings.used_cache, cacheReadableSize),
                     onClick = {
                         scope.launchNonCancellable {
                             try {
                                 val deletedFiles = chapterCache.clear()
                                 withUIContext {
-                                    context.toast(context.getString(R.string.cache_deleted, deletedFiles))
+                                    context.toast(context.localize(MR.strings.cache_deleted, deletedFiles))
                                     cacheReadableSizeSema++
                                 }
                             } catch (e: Throwable) {
                                 logcat(LogPriority.ERROR, e)
-                                withUIContext { context.toast(R.string.cache_delete_error) }
+                                withUIContext { context.toast(MR.strings.cache_delete_error) }
                             }
                         }
                     },
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = libraryPreferences.autoClearChapterCache(),
-                    title = stringResource(R.string.pref_auto_clear_chapter_cache),
+                    title = localize(MR.strings.pref_auto_clear_chapter_cache),
                 ),
             ),
         )
@@ -303,10 +302,10 @@ object SettingsDataScreen : SearchableSettings {
         }
 
         return Preference.PreferenceItem.CustomPreference(
-            title = stringResource(R.string.pref_storage_usage),
+            title = localize(MR.strings.pref_storage_usage),
         ) {
             BasePreferenceWidget(
-                title = stringResource(R.string.pref_storage_usage),
+                title = localize(MR.strings.pref_storage_usage),
                 subcomponent = {
                     // TODO: downloads, SD cards, bar representation?, i18n
                     Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) {

+ 33 - 35
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt

@@ -4,7 +4,6 @@ import android.content.Intent
 import android.os.Environment
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.collectAsState
@@ -14,19 +13,19 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.util.fastMap
 import androidx.core.net.toUri
 import com.hippo.unifile.UniFile
 import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.widget.TriStateListDialog
-import eu.kanade.tachiyomi.R
 import kotlinx.coroutines.runBlocking
 import tachiyomi.domain.category.interactor.GetCategories
 import tachiyomi.domain.category.model.Category
 import tachiyomi.domain.download.service.DownloadPreferences
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -36,8 +35,7 @@ object SettingsDownloadScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_downloads
+    override fun getTitleRes() = MR.strings.pref_category_downloads
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -49,16 +47,16 @@ object SettingsDownloadScreen : SearchableSettings {
             getDownloadLocationPreference(downloadPreferences = downloadPreferences),
             Preference.PreferenceItem.SwitchPreference(
                 pref = downloadPreferences.downloadOnlyOverWifi(),
-                title = stringResource(R.string.connected_to_wifi),
+                title = localize(MR.strings.connected_to_wifi),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = downloadPreferences.saveChaptersAsCBZ(),
-                title = stringResource(R.string.save_chapter_as_cbz),
+                title = localize(MR.strings.save_chapter_as_cbz),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = downloadPreferences.splitTallImages(),
-                title = stringResource(R.string.split_tall_images),
-                subtitle = stringResource(R.string.split_tall_images_summary),
+                title = localize(MR.strings.split_tall_images),
+                subtitle = localize(MR.strings.split_tall_images_summary),
             ),
             getDeleteChaptersGroup(
                 downloadPreferences = downloadPreferences,
@@ -99,15 +97,15 @@ object SettingsDownloadScreen : SearchableSettings {
 
         return Preference.PreferenceItem.ListPreference(
             pref = currentDirPref,
-            title = stringResource(R.string.pref_download_directory),
+            title = localize(MR.strings.pref_download_directory),
             subtitleProvider = { value, _ ->
                 remember(value) {
                     UniFile.fromUri(context, value.toUri())?.filePath
-                } ?: stringResource(R.string.invalid_location, value)
+                } ?: localize(MR.strings.invalid_location, value)
             },
             entries = mapOf(
                 defaultDirPair,
-                customDirEntryKey to stringResource(R.string.custom_dir),
+                customDirEntryKey to localize(MR.strings.custom_dir),
             ),
             onValueChanged = {
                 val default = it == defaultDirPair.first
@@ -121,7 +119,7 @@ object SettingsDownloadScreen : SearchableSettings {
 
     @Composable
     private fun rememberDefaultDownloadDir(): Pair<String, String> {
-        val appName = stringResource(R.string.app_name)
+        val appName = localize(MR.strings.app_name)
         return remember {
             val file = UniFile.fromFile(
                 File(
@@ -139,27 +137,27 @@ object SettingsDownloadScreen : SearchableSettings {
         categories: List<Category>,
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_delete_chapters),
+            title = localize(MR.strings.pref_category_delete_chapters),
             preferenceItems = listOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = downloadPreferences.removeAfterMarkedAsRead(),
-                    title = stringResource(R.string.pref_remove_after_marked_as_read),
+                    title = localize(MR.strings.pref_remove_after_marked_as_read),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = downloadPreferences.removeAfterReadSlots(),
-                    title = stringResource(R.string.pref_remove_after_read),
+                    title = localize(MR.strings.pref_remove_after_read),
                     entries = mapOf(
-                        -1 to stringResource(R.string.disabled),
-                        0 to stringResource(R.string.last_read_chapter),
-                        1 to stringResource(R.string.second_to_last),
-                        2 to stringResource(R.string.third_to_last),
-                        3 to stringResource(R.string.fourth_to_last),
-                        4 to stringResource(R.string.fifth_to_last),
+                        -1 to localize(MR.strings.disabled),
+                        0 to localize(MR.strings.last_read_chapter),
+                        1 to localize(MR.strings.second_to_last),
+                        2 to localize(MR.strings.third_to_last),
+                        3 to localize(MR.strings.fourth_to_last),
+                        4 to localize(MR.strings.fifth_to_last),
                     ),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = downloadPreferences.removeBookmarkedChapters(),
-                    title = stringResource(R.string.pref_remove_bookmarked_chapters),
+                    title = localize(MR.strings.pref_remove_bookmarked_chapters),
                 ),
                 getExcludedCategoriesPreference(
                     downloadPreferences = downloadPreferences,
@@ -176,7 +174,7 @@ object SettingsDownloadScreen : SearchableSettings {
     ): Preference.PreferenceItem.MultiSelectListPreference {
         return Preference.PreferenceItem.MultiSelectListPreference(
             pref = downloadPreferences.removeExcludeCategories(),
-            title = stringResource(R.string.pref_remove_exclude_categories),
+            title = localize(MR.strings.pref_remove_exclude_categories),
             entries = categories().associate { it.id.toString() to it.visualName },
         )
     }
@@ -197,8 +195,8 @@ object SettingsDownloadScreen : SearchableSettings {
         var showDialog by rememberSaveable { mutableStateOf(false) }
         if (showDialog) {
             TriStateListDialog(
-                title = stringResource(R.string.categories),
-                message = stringResource(R.string.pref_download_new_categories_details),
+                title = localize(MR.strings.categories),
+                message = localize(MR.strings.pref_download_new_categories_details),
                 items = allCategories,
                 initialChecked = included.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
                 initialInversed = excluded.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
@@ -213,14 +211,14 @@ object SettingsDownloadScreen : SearchableSettings {
         }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_auto_download),
+            title = localize(MR.strings.pref_category_auto_download),
             preferenceItems = listOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = downloadNewChaptersPref,
-                    title = stringResource(R.string.pref_download_new),
+                    title = localize(MR.strings.pref_download_new),
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.categories),
+                    title = localize(MR.strings.categories),
                     subtitle = getCategoriesLabel(
                         allCategories = allCategories,
                         included = included,
@@ -238,20 +236,20 @@ object SettingsDownloadScreen : SearchableSettings {
         downloadPreferences: DownloadPreferences,
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.download_ahead),
+            title = localize(MR.strings.download_ahead),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = downloadPreferences.autoDownloadWhileReading(),
-                    title = stringResource(R.string.auto_download_while_reading),
+                    title = localize(MR.strings.auto_download_while_reading),
                     entries = listOf(0, 2, 3, 5, 10).associateWith {
                         if (it == 0) {
-                            stringResource(R.string.disabled)
+                            localize(MR.strings.disabled)
                         } else {
-                            pluralStringResource(id = R.plurals.next_unread_chapters, count = it, it)
+                            localizePlural(MR.plurals.next_unread_chapters, count = it, it)
                         }
                     },
                 ),
-                Preference.PreferenceItem.InfoPreference(stringResource(R.string.download_ahead_info)),
+                Preference.PreferenceItem.InfoPreference(localize(MR.strings.download_ahead_info)),
             ),
         )
     }

+ 47 - 49
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.more.settings.screen
 
-import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.collectAsState
@@ -11,8 +10,6 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.util.fastMap
 import androidx.core.content.ContextCompat
 import cafe.adriel.voyager.navigator.LocalNavigator
@@ -21,7 +18,6 @@ import cafe.adriel.voyager.navigator.currentOrThrow
 import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.widget.TriStateListDialog
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.ui.category.CategoryScreen
 import kotlinx.coroutines.launch
@@ -37,6 +33,9 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
 import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
 import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
 import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -45,8 +44,7 @@ object SettingsLibraryScreen : SearchableSettings {
 
     @Composable
     @ReadOnlyComposable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_library
+    override fun getTitleRes() = MR.strings.pref_category_library
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -77,16 +75,16 @@ object SettingsLibraryScreen : SearchableSettings {
         // For default category
         val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
             allCategories.fastMap { it.id.toInt() }
-        val labels = listOf(stringResource(R.string.default_category_summary)) +
+        val labels = listOf(localize(MR.strings.default_category_summary)) +
             allCategories.fastMap { it.visualName(context) }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.categories),
+            title = localize(MR.strings.categories),
             preferenceItems = listOf(
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.action_edit_categories),
-                    subtitle = pluralStringResource(
-                        id = R.plurals.num_categories,
+                    title = localize(MR.strings.action_edit_categories),
+                    subtitle = localizePlural(
+                        MR.plurals.num_categories,
                         count = userCategoriesCount,
                         userCategoriesCount,
                     ),
@@ -94,13 +92,13 @@ object SettingsLibraryScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = libraryPreferences.defaultCategory(),
-                    title = stringResource(R.string.default_category),
-                    subtitle = selectedCategory?.visualName ?: stringResource(R.string.default_category_summary),
+                    title = localize(MR.strings.default_category),
+                    subtitle = selectedCategory?.visualName ?: localize(MR.strings.default_category_summary),
                     entries = ids.zip(labels).toMap(),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = libraryPreferences.categorizedDisplaySettings(),
-                    title = stringResource(R.string.categorized_display_settings),
+                    title = localize(MR.strings.categorized_display_settings),
                     onValueChanged = {
                         if (!it) {
                             scope.launch {
@@ -132,8 +130,8 @@ object SettingsLibraryScreen : SearchableSettings {
         var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
         if (showCategoriesDialog) {
             TriStateListDialog(
-                title = stringResource(R.string.categories),
-                message = stringResource(R.string.pref_library_update_categories_details),
+                title = localize(MR.strings.categories),
+                message = localize(MR.strings.pref_library_update_categories_details),
                 items = allCategories,
                 initialChecked = included.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
                 initialInversed = excluded.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
@@ -148,18 +146,18 @@ object SettingsLibraryScreen : SearchableSettings {
         }
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_library_update),
+            title = localize(MR.strings.pref_category_library_update),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = autoUpdateIntervalPref,
-                    title = stringResource(R.string.pref_library_update_interval),
+                    title = localize(MR.strings.pref_library_update_interval),
                     entries = mapOf(
-                        0 to stringResource(R.string.update_never),
-                        12 to stringResource(R.string.update_12hour),
-                        24 to stringResource(R.string.update_24hour),
-                        48 to stringResource(R.string.update_48hour),
-                        72 to stringResource(R.string.update_72hour),
-                        168 to stringResource(R.string.update_weekly),
+                        0 to localize(MR.strings.update_never),
+                        12 to localize(MR.strings.update_12hour),
+                        24 to localize(MR.strings.update_24hour),
+                        48 to localize(MR.strings.update_48hour),
+                        72 to localize(MR.strings.update_72hour),
+                        168 to localize(MR.strings.update_weekly),
                     ),
                     onValueChanged = {
                         LibraryUpdateJob.setupTask(context, it)
@@ -169,12 +167,12 @@ object SettingsLibraryScreen : SearchableSettings {
                 Preference.PreferenceItem.MultiSelectListPreference(
                     pref = libraryPreferences.autoUpdateDeviceRestrictions(),
                     enabled = autoUpdateInterval > 0,
-                    title = stringResource(R.string.pref_library_update_restriction),
-                    subtitle = stringResource(R.string.restrictions),
+                    title = localize(MR.strings.pref_library_update_restriction),
+                    subtitle = localize(MR.strings.restrictions),
                     entries = mapOf(
-                        DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
-                        DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
-                        DEVICE_CHARGING to stringResource(R.string.charging),
+                        DEVICE_ONLY_ON_WIFI to localize(MR.strings.connected_to_wifi),
+                        DEVICE_NETWORK_NOT_METERED to localize(MR.strings.network_not_metered),
+                        DEVICE_CHARGING to localize(MR.strings.charging),
                     ),
                     onValueChanged = {
                         // Post to event looper to allow the preference to be updated.
@@ -183,7 +181,7 @@ object SettingsLibraryScreen : SearchableSettings {
                     },
                 ),
                 Preference.PreferenceItem.TextPreference(
-                    title = stringResource(R.string.categories),
+                    title = localize(MR.strings.categories),
                     subtitle = getCategoriesLabel(
                         allCategories = allCategories,
                         included = included,
@@ -193,22 +191,22 @@ object SettingsLibraryScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = libraryPreferences.autoUpdateMetadata(),
-                    title = stringResource(R.string.pref_library_update_refresh_metadata),
-                    subtitle = stringResource(R.string.pref_library_update_refresh_metadata_summary),
+                    title = localize(MR.strings.pref_library_update_refresh_metadata),
+                    subtitle = localize(MR.strings.pref_library_update_refresh_metadata_summary),
                 ),
                 Preference.PreferenceItem.MultiSelectListPreference(
                     pref = libraryPreferences.autoUpdateMangaRestrictions(),
-                    title = stringResource(R.string.pref_library_update_manga_restriction),
+                    title = localize(MR.strings.pref_library_update_manga_restriction),
                     entries = mapOf(
-                        MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
-                        MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
-                        MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
-                        MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
+                        MANGA_HAS_UNREAD to localize(MR.strings.pref_update_only_completely_read),
+                        MANGA_NON_READ to localize(MR.strings.pref_update_only_started),
+                        MANGA_NON_COMPLETED to localize(MR.strings.pref_update_only_non_completed),
+                        MANGA_OUTSIDE_RELEASE_PERIOD to localize(MR.strings.pref_update_only_in_release_period),
                     ),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = libraryPreferences.newShowUpdatesCount(),
-                    title = stringResource(R.string.pref_library_update_show_tab_badge),
+                    title = localize(MR.strings.pref_library_update_show_tab_badge),
                 ),
             ),
         )
@@ -219,34 +217,34 @@ object SettingsLibraryScreen : SearchableSettings {
         libraryPreferences: LibraryPreferences,
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_chapter_swipe),
+            title = localize(MR.strings.pref_chapter_swipe),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = libraryPreferences.swipeToStartAction(),
-                    title = stringResource(R.string.pref_chapter_swipe_start),
+                    title = localize(MR.strings.pref_chapter_swipe_start),
                     entries = mapOf(
                         LibraryPreferences.ChapterSwipeAction.Disabled to
-                            stringResource(R.string.disabled),
+                            localize(MR.strings.disabled),
                         LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
-                            stringResource(R.string.action_bookmark),
+                            localize(MR.strings.action_bookmark),
                         LibraryPreferences.ChapterSwipeAction.ToggleRead to
-                            stringResource(R.string.action_mark_as_read),
+                            localize(MR.strings.action_mark_as_read),
                         LibraryPreferences.ChapterSwipeAction.Download to
-                            stringResource(R.string.action_download),
+                            localize(MR.strings.action_download),
                     ),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = libraryPreferences.swipeToEndAction(),
-                    title = stringResource(R.string.pref_chapter_swipe_end),
+                    title = localize(MR.strings.pref_chapter_swipe_end),
                     entries = mapOf(
                         LibraryPreferences.ChapterSwipeAction.Disabled to
-                            stringResource(R.string.disabled),
+                            localize(MR.strings.disabled),
                         LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
-                            stringResource(R.string.action_bookmark),
+                            localize(MR.strings.action_bookmark),
                         LibraryPreferences.ChapterSwipeAction.ToggleRead to
-                            stringResource(R.string.action_mark_as_read),
+                            localize(MR.strings.action_mark_as_read),
                         LibraryPreferences.ChapterSwipeAction.Download to
-                            stringResource(R.string.action_download),
+                            localize(MR.strings.action_download),
                     ),
                 ),
             ),

+ 29 - 31
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.more.settings.screen
 
-import androidx.annotation.StringRes
 import androidx.compose.foundation.background
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.padding
@@ -10,7 +9,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode
-import androidx.compose.material.icons.outlined.ChromeReaderMode
 import androidx.compose.material.icons.outlined.Code
 import androidx.compose.material.icons.outlined.CollectionsBookmark
 import androidx.compose.material.icons.outlined.Explore
@@ -34,21 +32,22 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.ColorUtils
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.Navigator
 import cafe.adriel.voyager.navigator.currentOrThrow
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.more.settings.screen.about.AboutScreen
 import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
 import eu.kanade.presentation.util.LocalBackPress
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
 
 object SettingsMainScreen : Screen() {
@@ -85,13 +84,13 @@ object SettingsMainScreen : Screen() {
             topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState),
             topBar = { scrollBehavior ->
                 AppBar(
-                    title = stringResource(R.string.label_settings),
+                    title = localize(MR.strings.label_settings),
                     navigateUp = backPress::invoke,
                     actions = {
                         AppBarActions(
                             persistentListOf(
                                 AppBar.Action(
-                                    title = stringResource(R.string.action_search),
+                                    title = localize(MR.strings.action_search),
                                     icon = Icons.Outlined.Search,
                                     onClick = { navigator.navigate(SettingsSearchScreen(), twoPane) },
                                 ),
@@ -148,7 +147,7 @@ object SettingsMainScreen : Screen() {
                         CompositionLocalProvider(LocalContentColor provides contentColor) {
                             TextPreferenceWidget(
                                 modifier = modifier,
-                                title = stringResource(item.titleRes),
+                                title = localize(item.titleRes),
                                 subtitle = item.formatSubtitle(),
                                 icon = item.icon,
                                 onPreferenceClick = { navigator.navigate(item.screen, twoPane) },
@@ -165,73 +164,72 @@ object SettingsMainScreen : Screen() {
     }
 
     private data class Item(
-        @StringRes val titleRes: Int,
-        @StringRes val subtitleRes: Int,
-        val formatSubtitle: @Composable () -> String = { stringResource(subtitleRes) },
+        val titleRes: StringResource,
+        val subtitleRes: StringResource? = null,
+        val formatSubtitle: @Composable () -> String? = { subtitleRes?.let { localize(it) } },
         val icon: ImageVector,
         val screen: VoyagerScreen,
     )
 
     private val items = listOf(
         Item(
-            titleRes = R.string.pref_category_appearance,
-            subtitleRes = R.string.pref_appearance_summary,
+            titleRes = MR.strings.pref_category_appearance,
+            subtitleRes = MR.strings.pref_appearance_summary,
             icon = Icons.Outlined.Palette,
             screen = SettingsAppearanceScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_library,
-            subtitleRes = R.string.pref_library_summary,
+            titleRes = MR.strings.pref_category_library,
+            subtitleRes = MR.strings.pref_library_summary,
             icon = Icons.Outlined.CollectionsBookmark,
             screen = SettingsLibraryScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_reader,
-            subtitleRes = R.string.pref_reader_summary,
+            titleRes = MR.strings.pref_category_reader,
+            subtitleRes = MR.strings.pref_reader_summary,
             icon = Icons.AutoMirrored.Outlined.ChromeReaderMode,
             screen = SettingsReaderScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_downloads,
-            subtitleRes = R.string.pref_downloads_summary,
+            titleRes = MR.strings.pref_category_downloads,
+            subtitleRes = MR.strings.pref_downloads_summary,
             icon = Icons.Outlined.GetApp,
             screen = SettingsDownloadScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_tracking,
-            subtitleRes = R.string.pref_tracking_summary,
+            titleRes = MR.strings.pref_category_tracking,
+            subtitleRes = MR.strings.pref_tracking_summary,
             icon = Icons.Outlined.Sync,
             screen = SettingsTrackingScreen,
         ),
         Item(
-            titleRes = R.string.browse,
-            subtitleRes = R.string.pref_browse_summary,
+            titleRes = MR.strings.browse,
+            subtitleRes = MR.strings.pref_browse_summary,
             icon = Icons.Outlined.Explore,
             screen = SettingsBrowseScreen,
         ),
         Item(
-            titleRes = R.string.label_data_storage,
-            subtitleRes = R.string.pref_backup_summary,
+            titleRes = MR.strings.label_data_storage,
+            subtitleRes = MR.strings.pref_backup_summary,
             icon = Icons.Outlined.Storage,
             screen = SettingsDataScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_security,
-            subtitleRes = R.string.pref_security_summary,
+            titleRes = MR.strings.pref_category_security,
+            subtitleRes = MR.strings.pref_security_summary,
             icon = Icons.Outlined.Security,
             screen = SettingsSecurityScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_advanced,
-            subtitleRes = R.string.pref_advanced_summary,
+            titleRes = MR.strings.pref_category_advanced,
+            subtitleRes = MR.strings.pref_advanced_summary,
             icon = Icons.Outlined.Code,
             screen = SettingsAdvancedScreen,
         ),
         Item(
-            titleRes = R.string.pref_category_about,
-            subtitleRes = 0,
+            titleRes = MR.strings.pref_category_about,
             formatSubtitle = {
-                "${stringResource(R.string.app_name)} ${AboutScreen.getVersionName(withBuildDate = false)}"
+                "${localize(MR.strings.app_name)} ${AboutScreen.getVersionName(withBuildDate = false)}"
             },
             icon = Icons.Outlined.Info,
             screen = AboutScreen,

+ 75 - 77
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt

@@ -1,18 +1,17 @@
 package eu.kanade.presentation.more.settings.screen
 
 import android.os.Build
-import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.more.settings.Preference
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -22,8 +21,7 @@ object SettingsReaderScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_reader
+    override fun getTitleRes() = MR.strings.pref_category_reader
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -31,43 +29,43 @@ object SettingsReaderScreen : SearchableSettings {
         return listOf(
             Preference.PreferenceItem.ListPreference(
                 pref = readerPref.defaultReadingMode(),
-                title = stringResource(R.string.pref_viewer_type),
+                title = localize(MR.strings.pref_viewer_type),
                 entries = ReadingMode.entries.drop(1)
-                    .associate { it.flagValue to stringResource(it.stringRes) },
+                    .associate { it.flagValue to localize(it.stringRes) },
             ),
             Preference.PreferenceItem.ListPreference(
                 pref = readerPref.doubleTapAnimSpeed(),
-                title = stringResource(R.string.pref_double_tap_anim_speed),
+                title = localize(MR.strings.pref_double_tap_anim_speed),
                 entries = mapOf(
-                    1 to stringResource(R.string.double_tap_anim_speed_0),
-                    500 to stringResource(R.string.double_tap_anim_speed_normal),
-                    250 to stringResource(R.string.double_tap_anim_speed_fast),
+                    1 to localize(MR.strings.double_tap_anim_speed_0),
+                    500 to localize(MR.strings.double_tap_anim_speed_normal),
+                    250 to localize(MR.strings.double_tap_anim_speed_fast),
                 ),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = readerPref.showReadingMode(),
-                title = stringResource(R.string.pref_show_reading_mode),
-                subtitle = stringResource(R.string.pref_show_reading_mode_summary),
+                title = localize(MR.strings.pref_show_reading_mode),
+                subtitle = localize(MR.strings.pref_show_reading_mode_summary),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = readerPref.showNavigationOverlayOnStart(),
-                title = stringResource(R.string.pref_show_navigation_mode),
-                subtitle = stringResource(R.string.pref_show_navigation_mode_summary),
+                title = localize(MR.strings.pref_show_navigation_mode),
+                subtitle = localize(MR.strings.pref_show_navigation_mode_summary),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = readerPref.trueColor(),
-                title = stringResource(R.string.pref_true_color),
-                subtitle = stringResource(R.string.pref_true_color_summary),
+                title = localize(MR.strings.pref_true_color),
+                subtitle = localize(MR.strings.pref_true_color_summary),
                 enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O,
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = readerPref.pageTransitions(),
-                title = stringResource(R.string.pref_page_transitions),
+                title = localize(MR.strings.pref_page_transitions),
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = readerPref.flashOnPageChange(),
-                title = stringResource(R.string.pref_flash_page),
-                subtitle = stringResource(R.string.pref_flash_page_summ),
+                title = localize(MR.strings.pref_flash_page),
+                subtitle = localize(MR.strings.pref_flash_page_summ),
             ),
             getDisplayGroup(readerPreferences = readerPref),
             getReadingGroup(readerPreferences = readerPref),
@@ -83,42 +81,42 @@ object SettingsReaderScreen : SearchableSettings {
         val fullscreenPref = readerPreferences.fullscreen()
         val fullscreen by fullscreenPref.collectAsState()
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_display),
+            title = localize(MR.strings.pref_category_display),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.defaultOrientationType(),
-                    title = stringResource(R.string.pref_rotation_type),
+                    title = localize(MR.strings.pref_rotation_type),
                     entries = ReaderOrientation.entries.drop(1)
-                        .associate { it.flagValue to stringResource(it.stringRes) },
+                        .associate { it.flagValue to localize(it.stringRes) },
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.readerTheme(),
-                    title = stringResource(R.string.pref_reader_theme),
+                    title = localize(MR.strings.pref_reader_theme),
                     entries = mapOf(
-                        1 to stringResource(R.string.black_background),
-                        2 to stringResource(R.string.gray_background),
-                        0 to stringResource(R.string.white_background),
-                        3 to stringResource(R.string.automatic_background),
+                        1 to localize(MR.strings.black_background),
+                        2 to localize(MR.strings.gray_background),
+                        0 to localize(MR.strings.white_background),
+                        3 to localize(MR.strings.automatic_background),
                     ),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = fullscreenPref,
-                    title = stringResource(R.string.pref_fullscreen),
+                    title = localize(MR.strings.pref_fullscreen),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.cutoutShort(),
-                    title = stringResource(R.string.pref_cutout_short),
+                    title = localize(MR.strings.pref_cutout_short),
                     enabled = fullscreen &&
                         Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
                         LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.keepScreenOn(),
-                    title = stringResource(R.string.pref_keep_screen_on),
+                    title = localize(MR.strings.pref_keep_screen_on),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.showPageNumber(),
-                    title = stringResource(R.string.pref_show_page_number),
+                    title = localize(MR.strings.pref_show_page_number),
                 ),
             ),
         )
@@ -127,23 +125,23 @@ object SettingsReaderScreen : SearchableSettings {
     @Composable
     private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_category_reading),
+            title = localize(MR.strings.pref_category_reading),
             preferenceItems = listOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.skipRead(),
-                    title = stringResource(R.string.pref_skip_read_chapters),
+                    title = localize(MR.strings.pref_skip_read_chapters),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.skipFiltered(),
-                    title = stringResource(R.string.pref_skip_filtered_chapters),
+                    title = localize(MR.strings.pref_skip_filtered_chapters),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.skipDupe(),
-                    title = stringResource(R.string.pref_skip_dupe_chapters),
+                    title = localize(MR.strings.pref_skip_dupe_chapters),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.alwaysShowChapterTransition(),
-                    title = stringResource(R.string.pref_always_show_chapter_transition),
+                    title = localize(MR.strings.pref_always_show_chapter_transition),
                 ),
             ),
         )
@@ -162,57 +160,57 @@ object SettingsReaderScreen : SearchableSettings {
         val rotateToFit by rotateToFitPref.collectAsState()
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pager_viewer),
+            title = localize(MR.strings.pager_viewer),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = navModePref,
-                    title = stringResource(R.string.pref_viewer_nav),
+                    title = localize(MR.strings.pref_viewer_nav),
                     entries = ReaderPreferences.TapZones
-                        .mapIndexed { index, it -> index to stringResource(it) }
+                        .mapIndexed { index, it -> index to localize(it) }
                         .toMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.pagerNavInverted(),
-                    title = stringResource(R.string.pref_read_with_tapping_inverted),
+                    title = localize(MR.strings.pref_read_with_tapping_inverted),
                     entries = listOf(
                         ReaderPreferences.TappingInvertMode.NONE,
                         ReaderPreferences.TappingInvertMode.HORIZONTAL,
                         ReaderPreferences.TappingInvertMode.VERTICAL,
                         ReaderPreferences.TappingInvertMode.BOTH,
-                    ).associateWith { stringResource(it.titleResId) },
+                    ).associateWith { localize(it.titleRes) },
                     enabled = navMode != 5,
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = imageScaleTypePref,
-                    title = stringResource(R.string.pref_image_scale_type),
+                    title = localize(MR.strings.pref_image_scale_type),
                     entries = ReaderPreferences.ImageScaleType
-                        .mapIndexed { index, it -> index + 1 to stringResource(it) }
+                        .mapIndexed { index, it -> index + 1 to localize(it) }
                         .toMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.zoomStart(),
-                    title = stringResource(R.string.pref_zoom_start),
+                    title = localize(MR.strings.pref_zoom_start),
                     entries = ReaderPreferences.ZoomStart
-                        .mapIndexed { index, it -> index + 1 to stringResource(it) }
+                        .mapIndexed { index, it -> index + 1 to localize(it) }
                         .toMap(),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.cropBorders(),
-                    title = stringResource(R.string.pref_crop_borders),
+                    title = localize(MR.strings.pref_crop_borders),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.landscapeZoom(),
-                    title = stringResource(R.string.pref_landscape_zoom),
+                    title = localize(MR.strings.pref_landscape_zoom),
                     enabled = imageScaleType == 1,
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.navigateToPan(),
-                    title = stringResource(R.string.pref_navigate_pan),
+                    title = localize(MR.strings.pref_navigate_pan),
                     enabled = navMode != 5,
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = dualPageSplitPref,
-                    title = stringResource(R.string.pref_dual_page_split),
+                    title = localize(MR.strings.pref_dual_page_split),
                     onValueChanged = {
                         rotateToFitPref.set(false)
                         true
@@ -220,13 +218,13 @@ object SettingsReaderScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.dualPageInvertPaged(),
-                    title = stringResource(R.string.pref_dual_page_invert),
-                    subtitle = stringResource(R.string.pref_dual_page_invert_summary),
+                    title = localize(MR.strings.pref_dual_page_invert),
+                    subtitle = localize(MR.strings.pref_dual_page_invert_summary),
                     enabled = dualPageSplit,
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = rotateToFitPref,
-                    title = stringResource(R.string.pref_page_rotate),
+                    title = localize(MR.strings.pref_page_rotate),
                     onValueChanged = {
                         dualPageSplitPref.set(false)
                         true
@@ -234,7 +232,7 @@ object SettingsReaderScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.dualPageRotateToFitInvert(),
-                    title = stringResource(R.string.pref_page_rotate_invert),
+                    title = localize(MR.strings.pref_page_rotate_invert),
                     enabled = rotateToFit,
                 ),
             ),
@@ -254,29 +252,29 @@ object SettingsReaderScreen : SearchableSettings {
         val webtoonSidePadding by webtoonSidePaddingPref.collectAsState()
 
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.webtoon_viewer),
+            title = localize(MR.strings.webtoon_viewer),
             preferenceItems = listOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = navModePref,
-                    title = stringResource(R.string.pref_viewer_nav),
+                    title = localize(MR.strings.pref_viewer_nav),
                     entries = ReaderPreferences.TapZones
-                        .mapIndexed { index, it -> index to stringResource(it) }
+                        .mapIndexed { index, it -> index to localize(it) }
                         .toMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.webtoonNavInverted(),
-                    title = stringResource(R.string.pref_read_with_tapping_inverted),
+                    title = localize(MR.strings.pref_read_with_tapping_inverted),
                     entries = listOf(
                         ReaderPreferences.TappingInvertMode.NONE,
                         ReaderPreferences.TappingInvertMode.HORIZONTAL,
                         ReaderPreferences.TappingInvertMode.VERTICAL,
                         ReaderPreferences.TappingInvertMode.BOTH,
-                    ).associateWith { stringResource(it.titleResId) },
+                    ).associateWith { localize(it.titleRes) },
                     enabled = navMode != 5,
                 ),
                 Preference.PreferenceItem.SliderPreference(
                     value = webtoonSidePadding,
-                    title = stringResource(R.string.pref_webtoon_side_padding),
+                    title = localize(MR.strings.pref_webtoon_side_padding),
                     subtitle = numberFormat.format(webtoonSidePadding / 100f),
                     min = ReaderPreferences.WEBTOON_PADDING_MIN,
                     max = ReaderPreferences.WEBTOON_PADDING_MAX,
@@ -287,31 +285,31 @@ object SettingsReaderScreen : SearchableSettings {
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.readerHideThreshold(),
-                    title = stringResource(R.string.pref_hide_threshold),
+                    title = localize(MR.strings.pref_hide_threshold),
                     entries = mapOf(
-                        ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(R.string.pref_highest),
-                        ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(R.string.pref_high),
-                        ReaderPreferences.ReaderHideThreshold.LOW to stringResource(R.string.pref_low),
-                        ReaderPreferences.ReaderHideThreshold.LOWEST to stringResource(R.string.pref_lowest),
+                        ReaderPreferences.ReaderHideThreshold.HIGHEST to localize(MR.strings.pref_highest),
+                        ReaderPreferences.ReaderHideThreshold.HIGH to localize(MR.strings.pref_high),
+                        ReaderPreferences.ReaderHideThreshold.LOW to localize(MR.strings.pref_low),
+                        ReaderPreferences.ReaderHideThreshold.LOWEST to localize(MR.strings.pref_lowest),
                     ),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.cropBordersWebtoon(),
-                    title = stringResource(R.string.pref_crop_borders),
+                    title = localize(MR.strings.pref_crop_borders),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = dualPageSplitPref,
-                    title = stringResource(R.string.pref_dual_page_split),
+                    title = localize(MR.strings.pref_dual_page_split),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.dualPageInvertWebtoon(),
-                    title = stringResource(R.string.pref_dual_page_invert),
-                    subtitle = stringResource(R.string.pref_dual_page_invert_summary),
+                    title = localize(MR.strings.pref_dual_page_invert),
+                    subtitle = localize(MR.strings.pref_dual_page_invert_summary),
                     enabled = dualPageSplit,
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.webtoonDoubleTapZoomEnabled(),
-                    title = stringResource(R.string.pref_double_tap_zoom),
+                    title = localize(MR.strings.pref_double_tap_zoom),
                     enabled = true,
                 ),
             ),
@@ -323,15 +321,15 @@ object SettingsReaderScreen : SearchableSettings {
         val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys()
         val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_reader_navigation),
+            title = localize(MR.strings.pref_reader_navigation),
             preferenceItems = listOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readWithVolumeKeysPref,
-                    title = stringResource(R.string.pref_read_with_volume_keys),
+                    title = localize(MR.strings.pref_read_with_volume_keys),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.readWithVolumeKeysInverted(),
-                    title = stringResource(R.string.pref_read_with_volume_keys_inverted),
+                    title = localize(MR.strings.pref_read_with_volume_keys_inverted),
                     enabled = readWithVolumeKeys,
                 ),
             ),
@@ -341,16 +339,16 @@ object SettingsReaderScreen : SearchableSettings {
     @Composable
     private fun getActionsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
-            title = stringResource(R.string.pref_reader_actions),
+            title = localize(MR.strings.pref_reader_actions),
             preferenceItems = listOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.readWithLongTap(),
-                    title = stringResource(R.string.pref_read_with_long_tap),
+                    title = localize(MR.strings.pref_read_with_long_tap),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.folderPerManga(),
-                    title = stringResource(R.string.pref_create_folder_per_manga),
-                    subtitle = stringResource(R.string.pref_create_folder_per_manga_summary),
+                    title = localize(MR.strings.pref_create_folder_per_manga),
+                    subtitle = localize(MR.strings.pref_create_folder_per_manga_summary),
                 ),
             ),
         )

+ 5 - 5
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSearchScreen.kt

@@ -41,7 +41,6 @@ import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.TextFieldValue
@@ -53,8 +52,9 @@ import cafe.adriel.voyager.navigator.currentOrThrow
 import eu.kanade.presentation.components.UpIcon
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.util.runOnEnterKeyPressed
 import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
@@ -118,7 +118,7 @@ class SettingsSearchScreen : Screen() {
                                 decorationBox = {
                                     if (textFieldValue.text.isEmpty()) {
                                         Text(
-                                            text = stringResource(R.string.action_search_settings),
+                                            text = localize(MR.strings.action_search_settings),
                                             color = MaterialTheme.colorScheme.onSurfaceVariant,
                                             style = MaterialTheme.typography.bodyLarge,
                                         )
@@ -222,7 +222,7 @@ private fun SearchResult(
         when {
             it == null -> {}
             it.isEmpty() -> {
-                EmptyScreen(stringResource(R.string.no_results_found))
+                EmptyScreen(localize(MR.strings.no_results_found))
             }
             else -> {
                 LazyColumn(
@@ -268,7 +268,7 @@ private fun SearchResult(
 private fun getIndex() = settingScreens
     .map { screen ->
         SettingsData(
-            title = stringResource(screen.getTitleRes()),
+            title = localize(screen.getTitleRes()),
             route = screen,
             contents = screen.getPreferences(),
         )

+ 16 - 17
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt

@@ -1,19 +1,19 @@
 package eu.kanade.presentation.more.settings.screen
 
-import androidx.annotation.StringRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.fragment.app.FragmentActivity
 import eu.kanade.presentation.more.settings.Preference
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.core.security.SecurityPreferences
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
+import tachiyomi.core.i18n.localize
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -22,8 +22,7 @@ object SettingsSecurityScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_security
+    override fun getTitleRes() = MR.strings.pref_category_security
 
     @Composable
     override fun getPreferences(): List<Preference> {
@@ -37,43 +36,43 @@ object SettingsSecurityScreen : SearchableSettings {
         return listOf(
             Preference.PreferenceItem.SwitchPreference(
                 pref = useAuthPref,
-                title = stringResource(R.string.lock_with_biometrics),
+                title = localize(MR.strings.lock_with_biometrics),
                 enabled = authSupported,
                 onValueChanged = {
                     (context as FragmentActivity).authenticate(
-                        title = context.getString(R.string.lock_with_biometrics),
+                        title = context.localize(MR.strings.lock_with_biometrics),
                     )
                 },
             ),
             Preference.PreferenceItem.ListPreference(
                 pref = securityPreferences.lockAppAfter(),
-                title = stringResource(R.string.lock_when_idle),
+                title = localize(MR.strings.lock_when_idle),
                 enabled = authSupported && useAuth,
                 entries = LockAfterValues
                     .associateWith {
                         when (it) {
-                            -1 -> stringResource(R.string.lock_never)
-                            0 -> stringResource(R.string.lock_always)
-                            else -> pluralStringResource(id = R.plurals.lock_after_mins, count = it, it)
+                            -1 -> localize(MR.strings.lock_never)
+                            0 -> localize(MR.strings.lock_always)
+                            else -> localizePlural(MR.plurals.lock_after_mins, count = it, it)
                         }
                     },
                 onValueChanged = {
                     (context as FragmentActivity).authenticate(
-                        title = context.getString(R.string.lock_when_idle),
+                        title = context.localize(MR.strings.lock_when_idle),
                     )
                 },
             ),
             Preference.PreferenceItem.SwitchPreference(
                 pref = securityPreferences.hideNotificationContent(),
-                title = stringResource(R.string.hide_notification_content),
+                title = localize(MR.strings.hide_notification_content),
             ),
             Preference.PreferenceItem.ListPreference(
                 pref = securityPreferences.secureScreen(),
-                title = stringResource(R.string.secure_screen),
+                title = localize(MR.strings.secure_screen),
                 entries = SecurityPreferences.SecureScreenMode.entries
-                    .associateWith { stringResource(it.titleResId) },
+                    .associateWith { localize(it.titleRes) },
             ),
-            Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)),
+            Preference.PreferenceItem.InfoPreference(localize(MR.strings.secure_screen_summary)),
         )
     }
 }

+ 27 - 29
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt

@@ -1,7 +1,6 @@
 package eu.kanade.presentation.more.settings.screen
 
 import android.content.Context
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -13,7 +12,6 @@ import androidx.compose.material.icons.automirrored.outlined.HelpOutline
 import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.material.icons.filled.VisibilityOff
 import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material.icons.outlined.HelpOutline
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
@@ -34,7 +32,6 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -42,9 +39,9 @@ import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.domain.track.service.TrackPreferences
 import eu.kanade.presentation.more.settings.Preference
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.EnhancedTracker
 import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.TrackerManager
@@ -57,7 +54,9 @@ import eu.kanade.tachiyomi.util.system.toast
 import tachiyomi.core.util.lang.launchIO
 import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.domain.source.service.SourceManager
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 
@@ -65,8 +64,7 @@ object SettingsTrackingScreen : SearchableSettings {
 
     @ReadOnlyComposable
     @Composable
-    @StringRes
-    override fun getTitleRes() = R.string.pref_category_tracking
+    override fun getTitleRes() = MR.strings.pref_category_tracking
 
     @Composable
     override fun RowScope.AppBarAction() {
@@ -74,7 +72,7 @@ object SettingsTrackingScreen : SearchableSettings {
         IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
             Icon(
                 imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
-                contentDescription = stringResource(R.string.tracking_guide),
+                contentDescription = localize(MR.strings.tracking_guide),
             )
         }
     }
@@ -111,10 +109,10 @@ object SettingsTrackingScreen : SearchableSettings {
                 val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
                 sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
             }
-        var enhancedTrackerInfo = stringResource(R.string.enhanced_tracking_info)
+        var enhancedTrackerInfo = localize(MR.strings.enhanced_tracking_info)
         if (enhancedTrackers.second.isNotEmpty()) {
-            val missingSourcesInfo = stringResource(
-                R.string.enhanced_services_not_installed,
+            val missingSourcesInfo = localize(
+                MR.strings.enhanced_services_not_installed,
                 enhancedTrackers.second.joinToString { it.name },
             )
             enhancedTrackerInfo += "\n\n$missingSourcesInfo"
@@ -123,10 +121,10 @@ object SettingsTrackingScreen : SearchableSettings {
         return listOf(
             Preference.PreferenceItem.SwitchPreference(
                 pref = trackPreferences.autoUpdateTrack(),
-                title = stringResource(R.string.pref_auto_update_manga_sync),
+                title = localize(MR.strings.pref_auto_update_manga_sync),
             ),
             Preference.PreferenceGroup(
-                title = stringResource(R.string.services),
+                title = localize(MR.strings.services),
                 preferenceItems = listOf(
                     Preference.PreferenceItem.TrackerPreference(
                         title = trackerManager.myAnimeList.name,
@@ -143,13 +141,13 @@ object SettingsTrackingScreen : SearchableSettings {
                     Preference.PreferenceItem.TrackerPreference(
                         title = trackerManager.kitsu.name,
                         tracker = trackerManager.kitsu,
-                        login = { dialog = LoginDialog(trackerManager.kitsu, R.string.email) },
+                        login = { dialog = LoginDialog(trackerManager.kitsu, MR.strings.email) },
                         logout = { dialog = LogoutDialog(trackerManager.kitsu) },
                     ),
                     Preference.PreferenceItem.TrackerPreference(
                         title = trackerManager.mangaUpdates.name,
                         tracker = trackerManager.mangaUpdates,
-                        login = { dialog = LoginDialog(trackerManager.mangaUpdates, R.string.username) },
+                        login = { dialog = LoginDialog(trackerManager.mangaUpdates, MR.strings.username) },
                         logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
                     ),
                     Preference.PreferenceItem.TrackerPreference(
@@ -164,11 +162,11 @@ object SettingsTrackingScreen : SearchableSettings {
                         login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
                         logout = { dialog = LogoutDialog(trackerManager.bangumi) },
                     ),
-                    Preference.PreferenceItem.InfoPreference(stringResource(R.string.tracking_info)),
+                    Preference.PreferenceItem.InfoPreference(localize(MR.strings.tracking_info)),
                 ),
             ),
             Preference.PreferenceGroup(
-                title = stringResource(R.string.enhanced_services),
+                title = localize(MR.strings.enhanced_services),
                 preferenceItems = enhancedTrackers.first
                     .map { service ->
                         Preference.PreferenceItem.TrackerPreference(
@@ -185,7 +183,7 @@ object SettingsTrackingScreen : SearchableSettings {
     @Composable
     private fun TrackingLoginDialog(
         tracker: Tracker,
-        @StringRes uNameStringRes: Int,
+        uNameStringRes: StringResource,
         onDismissRequest: () -> Unit,
     ) {
         val context = LocalContext.current
@@ -201,13 +199,13 @@ object SettingsTrackingScreen : SearchableSettings {
             title = {
                 Row(verticalAlignment = Alignment.CenterVertically) {
                     Text(
-                        text = stringResource(R.string.login_title, tracker.name),
+                        text = localize(MR.strings.login_title, tracker.name),
                         modifier = Modifier.weight(1f),
                     )
                     IconButton(onClick = onDismissRequest) {
                         Icon(
                             imageVector = Icons.Outlined.Close,
-                            contentDescription = stringResource(R.string.action_close),
+                            contentDescription = localize(MR.strings.action_close),
                         )
                     }
                 }
@@ -218,7 +216,7 @@ object SettingsTrackingScreen : SearchableSettings {
                         modifier = Modifier.fillMaxWidth(),
                         value = username,
                         onValueChange = { username = it },
-                        label = { Text(text = stringResource(uNameStringRes)) },
+                        label = { Text(text = localize(uNameStringRes)) },
                         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
                         singleLine = true,
                         isError = inputError && !processing,
@@ -229,7 +227,7 @@ object SettingsTrackingScreen : SearchableSettings {
                         modifier = Modifier.fillMaxWidth(),
                         value = password,
                         onValueChange = { password = it },
-                        label = { Text(text = stringResource(R.string.password)) },
+                        label = { Text(text = localize(MR.strings.password)) },
                         trailingIcon = {
                             IconButton(onClick = { hidePassword = !hidePassword }) {
                                 Icon(
@@ -275,8 +273,8 @@ object SettingsTrackingScreen : SearchableSettings {
                         }
                     },
                 ) {
-                    val id = if (processing) R.string.loading else R.string.login
-                    Text(text = stringResource(id))
+                    val id = if (processing) MR.strings.loading else MR.strings.login
+                    Text(text = localize(id))
                 }
             },
         )
@@ -290,7 +288,7 @@ object SettingsTrackingScreen : SearchableSettings {
     ): Boolean {
         return try {
             tracker.login(username, password)
-            withUIContext { context.toast(R.string.login_success) }
+            withUIContext { context.toast(MR.strings.login_success) }
             true
         } catch (e: Throwable) {
             tracker.logout()
@@ -309,7 +307,7 @@ object SettingsTrackingScreen : SearchableSettings {
             onDismissRequest = onDismissRequest,
             title = {
                 Text(
-                    text = stringResource(R.string.logout_title, tracker.name),
+                    text = localize(MR.strings.logout_title, tracker.name),
                     textAlign = TextAlign.Center,
                     modifier = Modifier.fillMaxWidth(),
                 )
@@ -320,21 +318,21 @@ object SettingsTrackingScreen : SearchableSettings {
                         modifier = Modifier.weight(1f),
                         onClick = onDismissRequest,
                     ) {
-                        Text(text = stringResource(R.string.action_cancel))
+                        Text(text = localize(MR.strings.action_cancel))
                     }
                     Button(
                         modifier = Modifier.weight(1f),
                         onClick = {
                             tracker.logout()
                             onDismissRequest()
-                            context.toast(R.string.logout_success)
+                            context.toast(MR.strings.logout_success)
                         },
                         colors = ButtonDefaults.buttonColors(
                             containerColor = MaterialTheme.colorScheme.error,
                             contentColor = MaterialTheme.colorScheme.onError,
                         ),
                     ) {
-                        Text(text = stringResource(R.string.logout))
+                        Text(text = localize(MR.strings.logout))
                     }
                 }
             },
@@ -344,7 +342,7 @@ object SettingsTrackingScreen : SearchableSettings {
 
 private data class LoginDialog(
     val tracker: Tracker,
-    @StringRes val uNameStringRes: Int,
+    val uNameStringRes: StringResource,
 )
 
 private data class LogoutDialog(

+ 12 - 12
app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt

@@ -19,7 +19,6 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
@@ -30,7 +29,6 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
 import eu.kanade.presentation.util.LocalBackPress
 import eu.kanade.presentation.util.Screen
 import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
 import eu.kanade.tachiyomi.data.updater.RELEASE_URL
 import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
@@ -44,9 +42,11 @@ import tachiyomi.core.util.lang.withIOContext
 import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.release.interactor.GetApplicationRelease
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LinkIcon
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.icons.CustomIcons
 import tachiyomi.presentation.core.icons.Discord
 import tachiyomi.presentation.core.icons.Facebook
@@ -74,7 +74,7 @@ object AboutScreen : Screen() {
         Scaffold(
             topBar = { scrollBehavior ->
                 AppBar(
-                    title = stringResource(R.string.pref_category_about),
+                    title = localize(MR.strings.pref_category_about),
                     navigateUp = if (handleBack != null) handleBack::invoke else null,
                     scrollBehavior = scrollBehavior,
                 )
@@ -89,7 +89,7 @@ object AboutScreen : Screen() {
 
                 item {
                     TextPreferenceWidget(
-                        title = stringResource(R.string.version),
+                        title = localize(MR.strings.version),
                         subtitle = getVersionName(withBuildDate = true),
                         onPreferenceClick = {
                             val deviceInfo = CrashLogUtil(context).getDebugInfo()
@@ -101,7 +101,7 @@ object AboutScreen : Screen() {
                 if (BuildConfig.INCLUDE_UPDATER) {
                     item {
                         TextPreferenceWidget(
-                            title = stringResource(R.string.check_for_updates),
+                            title = localize(MR.strings.check_for_updates),
                             widget = {
                                 AnimatedVisibility(visible = isCheckingUpdates) {
                                     CircularProgressIndicator(
@@ -140,7 +140,7 @@ object AboutScreen : Screen() {
                 if (!BuildConfig.DEBUG) {
                     item {
                         TextPreferenceWidget(
-                            title = stringResource(R.string.whats_new),
+                            title = localize(MR.strings.whats_new),
                             onPreferenceClick = { uriHandler.openUri(RELEASE_URL) },
                         )
                     }
@@ -148,21 +148,21 @@ object AboutScreen : Screen() {
 
                 item {
                     TextPreferenceWidget(
-                        title = stringResource(R.string.help_translate),
+                        title = localize(MR.strings.help_translate),
                         onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/docs/contribute#translation") },
                     )
                 }
 
                 item {
                     TextPreferenceWidget(
-                        title = stringResource(R.string.licenses),
+                        title = localize(MR.strings.licenses),
                         onPreferenceClick = { navigator.push(OpenSourceLicensesScreen()) },
                     )
                 }
 
                 item {
                     TextPreferenceWidget(
-                        title = stringResource(R.string.privacy_policy),
+                        title = localize(MR.strings.privacy_policy),
                         onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy/") },
                     )
                 }
@@ -175,7 +175,7 @@ object AboutScreen : Screen() {
                         horizontalArrangement = Arrangement.Center,
                     ) {
                         LinkIcon(
-                            label = stringResource(R.string.website),
+                            label = localize(MR.strings.website),
                             icon = Icons.Outlined.Public,
                             url = "https://tachiyomi.org",
                         )
@@ -226,10 +226,10 @@ object AboutScreen : Screen() {
                         onAvailableUpdate(result)
                     }
                     is GetApplicationRelease.Result.NoNewUpdate -> {
-                        context.toast(R.string.update_check_no_new_updates)
+                        context.toast(MR.strings.update_check_no_new_updates)
                     }
                     is GetApplicationRelease.Result.OsTooOld -> {
-                        context.toast(R.string.update_check_eol)
+                        context.toast(MR.strings.update_check_eol)
                     }
                     else -> {}
                 }

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLibraryLicenseScreen.kt

@@ -9,7 +9,6 @@ import androidx.compose.material.icons.filled.Public
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.core.text.HtmlCompat
@@ -19,9 +18,10 @@ import com.google.android.material.textview.MaterialTextView
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 
 class OpenSourceLibraryLicenseScreen(
     private val name: String,
@@ -44,7 +44,7 @@ class OpenSourceLibraryLicenseScreen(
                             AppBarActions(
                                 persistentListOf(
                                     AppBar.Action(
-                                        title = stringResource(R.string.website),
+                                        title = localize(MR.strings.website),
                                         icon = Icons.Default.Public,
                                         onClick = { uriHandler.openUri(website) },
                                     ),

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt

@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
 import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
@@ -12,8 +11,9 @@ import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
 import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 
 class OpenSourceLicensesScreen : Screen() {
 
@@ -23,7 +23,7 @@ class OpenSourceLicensesScreen : Screen() {
         Scaffold(
             topBar = { scrollBehavior ->
                 AppBar(
-                    title = stringResource(R.string.licenses),
+                    title = localize(MR.strings.licenses),
                     navigateUp = navigator::pop,
                     scrollBehavior = scrollBehavior,
                 )

+ 12 - 12
app/src/main/java/eu/kanade/presentation/more/settings/screen/advanced/ClearDatabaseScreen.kt

@@ -27,7 +27,6 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastMap
 import cafe.adriel.voyager.core.model.StateScreenModel
@@ -39,7 +38,6 @@ import eu.kanade.presentation.browse.components.SourceIcon
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.toast
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.flow.collectLatest
@@ -51,7 +49,9 @@ import tachiyomi.data.Database
 import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
 import tachiyomi.domain.source.model.Source
 import tachiyomi.domain.source.model.SourceWithCount
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import tachiyomi.presentation.core.util.selectedBackground
@@ -81,20 +81,20 @@ class ClearDatabaseScreen : Screen() {
                                         model.removeMangaBySourceId()
                                         model.clearSelection()
                                         model.hideConfirmation()
-                                        context.toast(R.string.clear_database_completed)
+                                        context.toast(MR.strings.clear_database_completed)
                                     }
                                 },
                             ) {
-                                Text(text = stringResource(R.string.action_ok))
+                                Text(text = localize(MR.strings.action_ok))
                             }
                         },
                         dismissButton = {
                             TextButton(onClick = model::hideConfirmation) {
-                                Text(text = stringResource(R.string.action_cancel))
+                                Text(text = localize(MR.strings.action_cancel))
                             }
                         },
                         text = {
-                            Text(text = stringResource(R.string.clear_database_confirmation))
+                            Text(text = localize(MR.strings.clear_database_confirmation))
                         },
                     )
                 }
@@ -102,19 +102,19 @@ class ClearDatabaseScreen : Screen() {
                 Scaffold(
                     topBar = { scrollBehavior ->
                         AppBar(
-                            title = stringResource(R.string.pref_clear_database),
+                            title = localize(MR.strings.pref_clear_database),
                             navigateUp = navigator::pop,
                             actions = {
                                 if (s.items.isNotEmpty()) {
                                     AppBarActions(
                                         actions = persistentListOf(
                                             AppBar.Action(
-                                                title = stringResource(R.string.action_select_all),
+                                                title = localize(MR.strings.action_select_all),
                                                 icon = Icons.Outlined.SelectAll,
                                                 onClick = model::selectAll,
                                             ),
                                             AppBar.Action(
-                                                title = stringResource(R.string.action_select_all),
+                                                title = localize(MR.strings.action_select_all),
                                                 icon = Icons.Outlined.FlipToBack,
                                                 onClick = model::invertSelection,
                                             ),
@@ -128,7 +128,7 @@ class ClearDatabaseScreen : Screen() {
                 ) { contentPadding ->
                     if (s.items.isEmpty()) {
                         EmptyScreen(
-                            message = stringResource(R.string.database_clean),
+                            message = localize(MR.strings.database_clean),
                             modifier = Modifier.padding(contentPadding),
                         )
                     } else {
@@ -160,7 +160,7 @@ class ClearDatabaseScreen : Screen() {
                                 enabled = s.selection.isNotEmpty(),
                             ) {
                                 Text(
-                                    text = stringResource(R.string.action_delete),
+                                    text = localize(MR.strings.action_delete),
                                     color = MaterialTheme.colorScheme.onPrimary,
                                 )
                             }
@@ -196,7 +196,7 @@ class ClearDatabaseScreen : Screen() {
                     text = source.visualName,
                     style = MaterialTheme.typography.bodyMedium,
                 )
-                Text(text = stringResource(R.string.clear_database_source_item_count, count))
+                Text(text = localize(MR.strings.clear_database_source_item_count, count))
             }
             Checkbox(
                 checked = isSelected,

+ 15 - 15
app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt

@@ -22,7 +22,6 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import cafe.adriel.voyager.core.model.StateScreenModel
 import cafe.adriel.voyager.core.model.rememberScreenModel
@@ -30,7 +29,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.BackupCreateFlags
 import eu.kanade.tachiyomi.data.backup.BackupCreateJob
 import eu.kanade.tachiyomi.data.backup.models.Backup
@@ -41,9 +39,11 @@ import kotlinx.collections.immutable.minus
 import kotlinx.collections.immutable.plus
 import kotlinx.collections.immutable.toPersistentSet
 import kotlinx.coroutines.flow.update
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 class CreateBackupScreen : Screen() {
 
@@ -71,7 +71,7 @@ class CreateBackupScreen : Screen() {
         Scaffold(
             topBar = {
                 AppBar(
-                    title = stringResource(R.string.pref_create_backup),
+                    title = localize(MR.strings.pref_create_backup),
                     navigateUp = navigator::pop,
                     scrollBehavior = it,
                 )
@@ -89,7 +89,7 @@ class CreateBackupScreen : Screen() {
                 ) {
                     item {
                         LabeledCheckbox(
-                            label = stringResource(R.string.manga),
+                            label = localize(MR.strings.manga),
                             checked = true,
                             onCheckedChange = {},
                             enabled = false,
@@ -98,7 +98,7 @@ class CreateBackupScreen : Screen() {
                     BackupChoices.forEach { (k, v) ->
                         item {
                             LabeledCheckbox(
-                                label = stringResource(v),
+                                label = localize(v),
                                 checked = state.flags.contains(k),
                                 onCheckedChange = {
                                     model.toggleFlag(k)
@@ -117,20 +117,20 @@ class CreateBackupScreen : Screen() {
                     onClick = {
                         if (!BackupCreateJob.isManualJobRunning(context)) {
                             if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
-                                context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
+                                context.toast(MR.strings.restore_miui_warning, Toast.LENGTH_LONG)
                             }
                             try {
                                 chooseBackupDir.launch(Backup.getFilename())
                             } catch (e: ActivityNotFoundException) {
-                                context.toast(R.string.file_picker_error)
+                                context.toast(MR.strings.file_picker_error)
                             }
                         } else {
-                            context.toast(R.string.backup_in_progress)
+                            context.toast(MR.strings.backup_in_progress)
                         }
                     },
                 ) {
                     Text(
-                        text = stringResource(R.string.action_create),
+                        text = localize(MR.strings.action_create),
                         color = MaterialTheme.colorScheme.onPrimary,
                     )
                 }
@@ -163,10 +163,10 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel
 }
 
 private val BackupChoices = mapOf(
-    BackupCreateFlags.BACKUP_CATEGORY to R.string.categories,
-    BackupCreateFlags.BACKUP_CHAPTER to R.string.chapters,
-    BackupCreateFlags.BACKUP_TRACK to R.string.track,
-    BackupCreateFlags.BACKUP_HISTORY to R.string.history,
-    BackupCreateFlags.BACKUP_APP_PREFS to R.string.app_settings,
-    BackupCreateFlags.BACKUP_SOURCE_PREFS to R.string.source_settings,
+    BackupCreateFlags.BACKUP_CATEGORY to MR.strings.categories,
+    BackupCreateFlags.BACKUP_CHAPTER to MR.strings.chapters,
+    BackupCreateFlags.BACKUP_TRACK to MR.strings.track,
+    BackupCreateFlags.BACKUP_HISTORY to MR.strings.history,
+    BackupCreateFlags.BACKUP_APP_PREFS to MR.strings.app_settings,
+    BackupCreateFlags.BACKUP_SOURCE_PREFS to MR.strings.source_settings,
 )

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/BackupSchemaScreen.kt

@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.unit.dp
 import cafe.adriel.voyager.navigator.LocalNavigator
@@ -18,12 +17,13 @@ import cafe.adriel.voyager.navigator.currentOrThrow
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.util.Screen
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.models.Backup
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 
 class BackupSchemaScreen : Screen() {
 
@@ -47,7 +47,7 @@ class BackupSchemaScreen : Screen() {
                         AppBarActions(
                             persistentListOf(
                                 AppBar.Action(
-                                    title = stringResource(R.string.action_copy_to_clipboard),
+                                    title = localize(MR.strings.action_copy_to_clipboard),
                                     icon = Icons.Default.ContentCopy,
                                     onClick = {
                                         context.copyToClipboard(title, schema)

+ 2 - 1
app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt

@@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.WebViewUtil
 import kotlinx.coroutines.guava.await
+import tachiyomi.i18n.MR
 
 class DebugInfoScreen : Screen() {
 
@@ -24,7 +25,7 @@ class DebugInfoScreen : Screen() {
     override fun Content() {
         val navigator = LocalNavigator.currentOrThrow
         PreferenceScaffold(
-            titleRes = R.string.pref_debug_info,
+            titleRes = MR.strings.pref_debug_info,
             onBackPressed = navigator::pop,
             itemsProvider = {
                 listOf(

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt

@@ -15,7 +15,6 @@ import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
@@ -30,14 +29,15 @@ import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.util.Screen
 import eu.kanade.presentation.util.ioCoroutineScope
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import eu.kanade.tachiyomi.util.system.workManager
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.Scaffold
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.plus
 
 class WorkerInfoScreen : Screen() {
@@ -65,7 +65,7 @@ class WorkerInfoScreen : Screen() {
                         AppBarActions(
                             persistentListOf(
                                 AppBar.Action(
-                                    title = stringResource(R.string.action_copy_to_clipboard),
+                                    title = localize(MR.strings.action_copy_to_clipboard),
                                     icon = Icons.Default.ContentCopy,
                                     onClick = {
                                         context.copyToClipboard(title, enqueued + finished + running)

+ 5 - 5
app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt

@@ -36,17 +36,17 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.unit.dp
 import eu.kanade.domain.ui.model.AppTheme
 import eu.kanade.presentation.manga.components.MangaCover
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 
 @Composable
@@ -76,7 +76,7 @@ private fun AppThemesList(
 ) {
     val appThemes = remember {
         AppTheme.entries
-            .filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
+            .filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
     }
     LazyRow(
         contentPadding = PaddingValues(horizontal = PrefsHorizontalPadding),
@@ -104,7 +104,7 @@ private fun AppThemesList(
                 Spacer(modifier = Modifier.height(8.dp))
 
                 Text(
-                    text = stringResource(appTheme.titleResId!!),
+                    text = localize(appTheme.titleRes!!),
                     modifier = Modifier
                         .fillMaxWidth()
                         .secondaryItemAlpha(),
@@ -167,7 +167,7 @@ fun AppThemePreviewItem(
                 if (selected) {
                     Icon(
                         imageVector = Icons.Filled.CheckCircle,
-                        contentDescription = stringResource(R.string.selected),
+                        contentDescription = localize(MR.strings.selected),
                         tint = MaterialTheme.colorScheme.primary,
                     )
                 }

+ 4 - 4
app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt

@@ -19,11 +19,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.window.DialogProperties
-import eu.kanade.tachiyomi.R
 import kotlinx.coroutines.launch
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun EditTextPreferenceWidget(
@@ -83,12 +83,12 @@ fun EditTextPreferenceWidget(
                         }
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_ok))
+                    Text(text = localize(MR.strings.action_ok))
                 }
             },
             dismissButton = {
                 TextButton(onClick = onDismissRequest) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
             },
         )

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt

@@ -11,11 +11,11 @@ import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 
 @Composable
@@ -45,7 +45,7 @@ internal fun InfoWidget(text: String) {
 private fun InfoWidgetPreview() {
     TachiyomiTheme {
         Surface {
-            InfoWidget(text = stringResource(R.string.download_ahead_info))
+            InfoWidget(text = localize(MR.strings.download_ahead_info))
         }
     }
 }

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt

@@ -22,10 +22,10 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrolledToStart
 
@@ -75,7 +75,7 @@ fun <T> ListPreferenceWidget(
             },
             confirmButton = {
                 TextButton(onClick = { isDialogShown = false }) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
             },
         )

+ 4 - 4
app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt

@@ -10,11 +10,11 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.toMutableStateList
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.window.DialogProperties
 import eu.kanade.presentation.more.settings.Preference
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun MultiSelectListPreferenceWidget(
@@ -70,12 +70,12 @@ fun MultiSelectListPreferenceWidget(
                         isDialogShown = false
                     },
                 ) {
-                    Text(text = stringResource(R.string.action_ok))
+                    Text(text = localize(MR.strings.action_ok))
                 }
             },
             dismissButton = {
                 TextButton(onClick = { isDialogShown = false }) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
             },
         )

+ 3 - 3
app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt

@@ -15,12 +15,12 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
 import eu.kanade.presentation.track.components.TrackLogoIcon
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.Tracker
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun TrackingPreferenceWidget(
@@ -55,7 +55,7 @@ fun TrackingPreferenceWidget(
                         .padding(4.dp)
                         .size(32.dp),
                     tint = Color(0xFF4CAF50),
-                    contentDescription = stringResource(R.string.login_success),
+                    contentDescription = localize(MR.strings.login_success),
                 )
             }
         }

+ 8 - 8
app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt

@@ -27,9 +27,9 @@ import androidx.compose.runtime.toMutableStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrolledToStart
 
@@ -102,11 +102,11 @@ fun <T> TriStateListDialog(
                                     } else {
                                         MaterialTheme.colorScheme.primary
                                     },
-                                    contentDescription = stringResource(
+                                    contentDescription = localize(
                                         when (state) {
-                                            State.UNCHECKED -> R.string.not_selected
-                                            State.CHECKED -> R.string.selected
-                                            State.INVERSED -> R.string.disabled
+                                            State.UNCHECKED -> MR.strings.not_selected
+                                            State.CHECKED -> MR.strings.selected
+                                            State.INVERSED -> MR.strings.disabled
                                         },
                                     ),
                                 )
@@ -130,7 +130,7 @@ fun <T> TriStateListDialog(
         },
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text(text = stringResource(R.string.action_cancel))
+                Text(text = localize(MR.strings.action_cancel))
             }
         },
         confirmButton = {
@@ -145,7 +145,7 @@ fun <T> TriStateListDialog(
                     onValueChanged(included, excluded)
                 },
             ) {
-                Text(text = stringResource(R.string.action_ok))
+                Text(text = localize(MR.strings.action_ok))
             }
         },
     )

+ 20 - 20
app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt

@@ -13,14 +13,14 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import eu.kanade.presentation.more.stats.components.StatsItem
 import eu.kanade.presentation.more.stats.components.StatsOverviewItem
 import eu.kanade.presentation.more.stats.components.StatsSection
 import eu.kanade.presentation.more.stats.data.StatsData
 import eu.kanade.presentation.util.toDurationString
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import java.util.Locale
 import kotlin.time.DurationUnit
 import kotlin.time.toDuration
@@ -55,28 +55,28 @@ fun StatsScreenContent(
 private fun OverviewSection(
     data: StatsData.Overview,
 ) {
-    val none = stringResource(R.string.none)
+    val none = localize(MR.strings.none)
     val context = LocalContext.current
     val readDurationString = remember(data.totalReadDuration) {
         data.totalReadDuration
             .toDuration(DurationUnit.MILLISECONDS)
             .toDurationString(context, fallback = none)
     }
-    StatsSection(R.string.label_overview_section) {
+    StatsSection(MR.strings.label_overview_section) {
         Row {
             StatsOverviewItem(
                 title = data.libraryMangaCount.toString(),
-                subtitle = stringResource(R.string.in_library),
+                subtitle = localize(MR.strings.in_library),
                 icon = Icons.Outlined.CollectionsBookmark,
             )
             StatsOverviewItem(
                 title = data.completedMangaCount.toString(),
-                subtitle = stringResource(R.string.label_completed_titles),
+                subtitle = localize(MR.strings.label_completed_titles),
                 icon = Icons.Outlined.LocalLibrary,
             )
             StatsOverviewItem(
                 title = readDurationString,
-                subtitle = stringResource(R.string.label_read_duration),
+                subtitle = localize(MR.strings.label_read_duration),
                 icon = Icons.Outlined.Schedule,
             )
         }
@@ -87,19 +87,19 @@ private fun OverviewSection(
 private fun TitlesStats(
     data: StatsData.Titles,
 ) {
-    StatsSection(R.string.label_titles_section) {
+    StatsSection(MR.strings.label_titles_section) {
         Row {
             StatsItem(
                 data.globalUpdateItemCount.toString(),
-                stringResource(R.string.label_titles_in_global_update),
+                localize(MR.strings.label_titles_in_global_update),
             )
             StatsItem(
                 data.startedMangaCount.toString(),
-                stringResource(R.string.label_started),
+                localize(MR.strings.label_started),
             )
             StatsItem(
                 data.localMangaCount.toString(),
-                stringResource(R.string.label_local),
+                localize(MR.strings.label_local),
             )
         }
     }
@@ -109,19 +109,19 @@ private fun TitlesStats(
 private fun ChapterStats(
     data: StatsData.Chapters,
 ) {
-    StatsSection(R.string.chapters) {
+    StatsSection(MR.strings.chapters) {
         Row {
             StatsItem(
                 data.totalChapterCount.toString(),
-                stringResource(R.string.label_total_chapters),
+                localize(MR.strings.label_total_chapters),
             )
             StatsItem(
                 data.readChapterCount.toString(),
-                stringResource(R.string.label_read_chapters),
+                localize(MR.strings.label_read_chapters),
             )
             StatsItem(
                 data.downloadCount.toString(),
-                stringResource(R.string.label_downloaded),
+                localize(MR.strings.label_downloaded),
             )
         }
     }
@@ -131,7 +131,7 @@ private fun ChapterStats(
 private fun TrackerStats(
     data: StatsData.Trackers,
 ) {
-    val notApplicable = stringResource(R.string.not_applicable)
+    val notApplicable = localize(MR.strings.not_applicable)
     val meanScoreStr = remember(data.trackedTitleCount, data.meanScore) {
         if (data.trackedTitleCount > 0 && !data.meanScore.isNaN()) {
             // All other numbers are localized in English
@@ -140,19 +140,19 @@ private fun TrackerStats(
             notApplicable
         }
     }
-    StatsSection(R.string.label_tracker_section) {
+    StatsSection(MR.strings.label_tracker_section) {
         Row {
             StatsItem(
                 data.trackedTitleCount.toString(),
-                stringResource(R.string.label_tracked_titles),
+                localize(MR.strings.label_tracked_titles),
             )
             StatsItem(
                 meanScoreStr,
-                stringResource(R.string.label_mean_score),
+                localize(MR.strings.label_mean_score),
             )
             StatsItem(
                 data.trackerCount.toString(),
-                stringResource(R.string.label_used),
+                localize(MR.strings.label_used),
             )
         }
     }

+ 4 - 4
app/src/main/java/eu/kanade/presentation/more/stats/components/StatsSection.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.more.stats.components
 
-import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -9,17 +8,18 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
+import dev.icerock.moko.resources.StringResource
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun StatsSection(
-    @StringRes titleRes: Int,
+    titleRes: StringResource,
     content: @Composable () -> Unit,
 ) {
     Text(
         modifier = Modifier.padding(horizontal = MaterialTheme.padding.extraLarge),
-        text = stringResource(titleRes),
+        text = localize(titleRes),
         style = MaterialTheme.typography.titleSmall,
     )
     ElevatedCard(

+ 11 - 11
app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt

@@ -26,8 +26,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.pluralStringResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.Placeholder
 import androidx.compose.ui.text.PlaceholderVerticalAlign
 import androidx.compose.ui.text.buildAnnotatedString
@@ -36,13 +34,15 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.ChapterImpl
 import eu.kanade.tachiyomi.data.database.models.toDomainChapter
 import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 import tachiyomi.domain.chapter.service.calculateChapterGap
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
+import tachiyomi.presentation.core.i18n.localizePlural
 import tachiyomi.presentation.core.util.secondaryItemAlpha
 
 @Composable
@@ -58,25 +58,25 @@ fun ChapterTransition(
         when (transition) {
             is ChapterTransition.Prev -> {
                 TransitionText(
-                    topLabel = stringResource(R.string.transition_previous),
+                    topLabel = localize(MR.strings.transition_previous),
                     topChapter = goingToChapter,
                     topChapterDownloaded = goingToChapterDownloaded,
-                    bottomLabel = stringResource(R.string.transition_current),
+                    bottomLabel = localize(MR.strings.transition_current),
                     bottomChapter = currChapter,
                     bottomChapterDownloaded = currChapterDownloaded,
-                    fallbackLabel = stringResource(R.string.transition_no_previous),
+                    fallbackLabel = localize(MR.strings.transition_no_previous),
                     chapterGap = calculateChapterGap(currChapter.toDomainChapter(), goingToChapter?.toDomainChapter()),
                 )
             }
             is ChapterTransition.Next -> {
                 TransitionText(
-                    topLabel = stringResource(R.string.transition_finished),
+                    topLabel = localize(MR.strings.transition_finished),
                     topChapter = currChapter,
                     topChapterDownloaded = currChapterDownloaded,
-                    bottomLabel = stringResource(R.string.transition_next),
+                    bottomLabel = localize(MR.strings.transition_next),
                     bottomChapter = goingToChapter,
                     bottomChapterDownloaded = goingToChapterDownloaded,
-                    fallbackLabel = stringResource(R.string.transition_no_next),
+                    fallbackLabel = localize(MR.strings.transition_no_next),
                     chapterGap = calculateChapterGap(goingToChapter?.toDomainChapter(), currChapter.toDomainChapter()),
                 )
             }
@@ -191,7 +191,7 @@ private fun ChapterGapWarning(
             )
 
             Text(
-                text = pluralStringResource(R.plurals.missing_chapters_warning, count = gapCount, gapCount),
+                text = localizePlural(MR.plurals.missing_chapters_warning, count = gapCount, gapCount),
                 style = MaterialTheme.typography.bodyMedium,
             )
         }
@@ -245,7 +245,7 @@ private fun ChapterText(
                 ) {
                     Icon(
                         imageVector = Icons.Filled.CheckCircle,
-                        contentDescription = stringResource(R.string.label_downloaded),
+                        contentDescription = localize(MR.strings.label_downloaded),
                     )
                 },
             ),

+ 6 - 5
app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt

@@ -12,18 +12,19 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.vectorResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.domain.manga.model.readerOrientation
 import eu.kanade.presentation.components.AdaptiveSheet
 import eu.kanade.presentation.reader.components.ModeSelectionDialog
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.SettingsIconGrid
 import tachiyomi.presentation.core.components.material.IconToggleButton
+import tachiyomi.presentation.core.i18n.localize
 
 private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - ReaderOrientation.DEFAULT
 
@@ -31,7 +32,7 @@ private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - Reade
 fun OrientationSelectDialog(
     onDismissRequest: () -> Unit,
     screenModel: ReaderSettingsScreenModel,
-    onChange: (Int) -> Unit,
+    onChange: (StringResource) -> Unit,
 ) {
     val manga by screenModel.mangaFlow.collectAsState()
     val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
@@ -63,7 +64,7 @@ private fun DialogContent(
         }.takeIf { orientation != ReaderOrientation.DEFAULT },
         onApply = { onChangeOrientation(selected) },
     ) {
-        SettingsIconGrid(R.string.rotation_type) {
+        SettingsIconGrid(MR.strings.rotation_type) {
             items(ReaderOrientationsWithoutDefault) { mode ->
                 IconToggleButton(
                     checked = mode == selected,
@@ -72,7 +73,7 @@ private fun DialogContent(
                     },
                     modifier = Modifier.fillMaxWidth(),
                     imageVector = ImageVector.vectorResource(mode.iconRes),
-                    title = stringResource(mode.stringRes),
+                    title = localize(mode.stringRes),
                 )
             }
         }

+ 8 - 8
app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt

@@ -17,12 +17,12 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.components.AdaptiveSheet
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ActionButton
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun ReaderPageActionsDialog(
@@ -42,13 +42,13 @@ fun ReaderPageActionsDialog(
         ) {
             ActionButton(
                 modifier = Modifier.weight(1f),
-                title = stringResource(R.string.set_as_cover),
+                title = localize(MR.strings.set_as_cover),
                 icon = Icons.Outlined.Photo,
                 onClick = { showSetCoverDialog = true },
             )
             ActionButton(
                 modifier = Modifier.weight(1f),
-                title = stringResource(R.string.action_share),
+                title = localize(MR.strings.action_share),
                 icon = Icons.Outlined.Share,
                 onClick = {
                     onShare()
@@ -57,7 +57,7 @@ fun ReaderPageActionsDialog(
             )
             ActionButton(
                 modifier = Modifier.weight(1f),
-                title = stringResource(R.string.action_save),
+                title = localize(MR.strings.action_save),
                 icon = Icons.Outlined.Save,
                 onClick = {
                     onSave()
@@ -85,16 +85,16 @@ private fun SetCoverDialog(
 ) {
     AlertDialog(
         text = {
-            Text(stringResource(R.string.confirm_set_image_as_cover))
+            Text(localize(MR.strings.confirm_set_image_as_cover))
         },
         confirmButton = {
             TextButton(onClick = onConfirm) {
-                Text(stringResource(R.string.action_ok))
+                Text(localize(MR.strings.action_ok))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismiss) {
-                Text(stringResource(R.string.action_cancel))
+                Text(localize(MR.strings.action_cancel))
             }
         },
         onDismissRequest = onDismiss,

+ 6 - 5
app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt

@@ -12,18 +12,19 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.vectorResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.domain.manga.model.readingMode
 import eu.kanade.presentation.components.AdaptiveSheet
 import eu.kanade.presentation.reader.components.ModeSelectionDialog
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.SettingsIconGrid
 import tachiyomi.presentation.core.components.material.IconToggleButton
+import tachiyomi.presentation.core.i18n.localize
 
 private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAULT
 
@@ -31,7 +32,7 @@ private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAU
 fun ReadingModeSelectDialog(
     onDismissRequest: () -> Unit,
     screenModel: ReaderSettingsScreenModel,
-    onChange: (Int) -> Unit,
+    onChange: (StringResource) -> Unit,
 ) {
     val manga by screenModel.mangaFlow.collectAsState()
     val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
@@ -59,7 +60,7 @@ private fun DialogContent(
         onUseDefault = { onChangeReadingMode(ReadingMode.DEFAULT) }.takeIf { readingMode != ReadingMode.DEFAULT },
         onApply = { onChangeReadingMode(selected) },
     ) {
-        SettingsIconGrid(R.string.pref_category_reading_mode) {
+        SettingsIconGrid(MR.strings.pref_category_reading_mode) {
             items(ReadingModesWithoutDefault) { mode ->
                 IconToggleButton(
                     checked = mode == selected,
@@ -68,7 +69,7 @@ private fun DialogContent(
                     },
                     modifier = Modifier.fillMaxWidth(),
                     imageVector = ImageVector.vectorResource(mode.iconRes),
-                    title = stringResource(mode.stringRes),
+                    title = localize(mode.stringRes),
                 )
             }
         }

+ 6 - 5
app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt

@@ -14,11 +14,12 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun BottomReaderBar(
@@ -42,28 +43,28 @@ fun BottomReaderBar(
         IconButton(onClick = onClickReadingMode) {
             Icon(
                 painter = painterResource(readingMode.iconRes),
-                contentDescription = stringResource(R.string.viewer),
+                contentDescription = localize(MR.strings.viewer),
             )
         }
 
         IconButton(onClick = onClickOrientation) {
             Icon(
                 painter = painterResource(orientation.iconRes),
-                contentDescription = stringResource(R.string.rotation_type),
+                contentDescription = localize(MR.strings.rotation_type),
             )
         }
 
         IconButton(onClick = onClickCropBorder) {
             Icon(
                 painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp),
-                contentDescription = stringResource(R.string.pref_crop_borders),
+                contentDescription = localize(MR.strings.pref_crop_borders),
             )
         }
 
         IconButton(onClick = onClickSettings) {
             Icon(
                 imageVector = Icons.Outlined.Settings,
-                contentDescription = stringResource(R.string.action_settings),
+                contentDescription = localize(MR.strings.action_settings),
             )
         }
     }

+ 7 - 7
app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt

@@ -18,18 +18,18 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.surfaceColorAtElevation
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.AppBarActions
 import eu.kanade.presentation.reader.components.ChapterNavigator
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
 import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 private val animationSpec = tween<IntOffset>(200)
 
@@ -103,11 +103,11 @@ fun ReaderAppBars(
                             .apply {
                                 add(
                                     AppBar.Action(
-                                        title = stringResource(
+                                        title = localize(
                                             if (bookmarked) {
-                                                R.string.action_remove_bookmark
+                                                MR.strings.action_remove_bookmark
                                             } else {
-                                                R.string.action_bookmark
+                                                MR.strings.action_bookmark
                                             },
                                         ),
                                         icon = if (bookmarked) {
@@ -121,7 +121,7 @@ fun ReaderAppBars(
                                 onOpenInWebView?.let {
                                     add(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_open_in_web_view),
+                                            title = localize(MR.strings.action_open_in_web_view),
                                             onClick = it,
                                         ),
                                     )
@@ -129,7 +129,7 @@ fun ReaderAppBars(
                                 onShare?.let {
                                     add(
                                         AppBar.OverflowAction(
-                                            title = stringResource(R.string.action_share),
+                                            title = localize(MR.strings.action_share),
                                             onClick = it,
                                         ),
                                     )

+ 6 - 6
app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt

@@ -30,11 +30,11 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.util.isTabletUi
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import kotlin.math.roundToInt
 
 @Composable
@@ -77,8 +77,8 @@ fun ChapterNavigator(
             ) {
                 Icon(
                     imageVector = Icons.Outlined.SkipPrevious,
-                    contentDescription = stringResource(
-                        if (isRtl) R.string.action_next_chapter else R.string.action_previous_chapter,
+                    contentDescription = localize(
+                        if (isRtl) MR.strings.action_next_chapter else MR.strings.action_previous_chapter,
                     ),
                 )
             }
@@ -129,8 +129,8 @@ fun ChapterNavigator(
             ) {
                 Icon(
                     imageVector = Icons.Outlined.SkipNext,
-                    contentDescription = stringResource(
-                        if (isRtl) R.string.action_previous_chapter else R.string.action_next_chapter,
+                    contentDescription = localize(
+                        if (isRtl) MR.strings.action_previous_chapter else MR.strings.action_next_chapter,
                     ),
                 )
             }

+ 4 - 4
app/src/main/java/eu/kanade/presentation/reader/components/ModeSelectionDialog.kt

@@ -16,12 +16,12 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.SettingsItemsPaddings
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun ModeSelectionDialog(
@@ -40,7 +40,7 @@ fun ModeSelectionDialog(
             ) {
                 onUseDefault?.let {
                     OutlinedButton(onClick = it) {
-                        Text(text = stringResource(R.string.action_revert_to_default))
+                        Text(text = localize(MR.strings.action_revert_to_default))
                     }
                 }
 
@@ -57,7 +57,7 @@ fun ModeSelectionDialog(
                             imageVector = Icons.Outlined.Check,
                             contentDescription = null,
                         )
-                        Text(text = stringResource(R.string.action_apply))
+                        Text(text = localize(MR.strings.action_apply))
                     }
                 }
             }

+ 19 - 19
app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt

@@ -6,17 +6,17 @@ import androidx.compose.material3.FilterChip
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.res.stringResource
 import androidx.core.graphics.alpha
 import androidx.core.graphics.blue
 import androidx.core.graphics.green
 import androidx.core.graphics.red
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
 import tachiyomi.core.preference.getAndSet
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.CheckboxItem
 import tachiyomi.presentation.core.components.SettingsChipRow
 import tachiyomi.presentation.core.components.SliderItem
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 
 @Composable
@@ -24,25 +24,25 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
     val colorFilterModes = buildList {
         addAll(
             listOf(
-                R.string.label_default,
-                R.string.filter_mode_multiply,
-                R.string.filter_mode_screen,
+                MR.strings.label_default,
+                MR.strings.filter_mode_multiply,
+                MR.strings.filter_mode_screen,
             ),
         )
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             addAll(
                 listOf(
-                    R.string.filter_mode_overlay,
-                    R.string.filter_mode_lighten,
-                    R.string.filter_mode_darken,
+                    MR.strings.filter_mode_overlay,
+                    MR.strings.filter_mode_lighten,
+                    MR.strings.filter_mode_darken,
                 ),
             )
         }
-    }.map { stringResource(it) }
+    }.map { localize(it) }
 
     val customBrightness by screenModel.preferences.customBrightness().collectAsState()
     CheckboxItem(
-        label = stringResource(R.string.pref_custom_brightness),
+        label = localize(MR.strings.pref_custom_brightness),
         pref = screenModel.preferences.customBrightness(),
     )
 
@@ -55,7 +55,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
     if (customBrightness) {
         val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
         SliderItem(
-            label = stringResource(R.string.pref_custom_brightness),
+            label = localize(MR.strings.pref_custom_brightness),
             min = -75,
             max = 100,
             value = customBrightnessValue,
@@ -66,13 +66,13 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
 
     val colorFilter by screenModel.preferences.colorFilter().collectAsState()
     CheckboxItem(
-        label = stringResource(R.string.pref_custom_color_filter),
+        label = localize(MR.strings.pref_custom_color_filter),
         pref = screenModel.preferences.colorFilter(),
     )
     if (colorFilter) {
         val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
         SliderItem(
-            label = stringResource(R.string.color_filter_r_value),
+            label = localize(MR.strings.color_filter_r_value),
             max = 255,
             value = colorFilterValue.red,
             valueText = colorFilterValue.red.toString(),
@@ -83,7 +83,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
             },
         )
         SliderItem(
-            label = stringResource(R.string.color_filter_g_value),
+            label = localize(MR.strings.color_filter_g_value),
             max = 255,
             value = colorFilterValue.green,
             valueText = colorFilterValue.green.toString(),
@@ -94,7 +94,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
             },
         )
         SliderItem(
-            label = stringResource(R.string.color_filter_b_value),
+            label = localize(MR.strings.color_filter_b_value),
             max = 255,
             value = colorFilterValue.blue,
             valueText = colorFilterValue.blue.toString(),
@@ -105,7 +105,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
             },
         )
         SliderItem(
-            label = stringResource(R.string.color_filter_a_value),
+            label = localize(MR.strings.color_filter_a_value),
             max = 255,
             value = colorFilterValue.alpha,
             valueText = colorFilterValue.alpha.toString(),
@@ -117,7 +117,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
         )
 
         val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
-        SettingsChipRow(R.string.pref_color_filter_mode) {
+        SettingsChipRow(MR.strings.pref_color_filter_mode) {
             colorFilterModes.mapIndexed { index, it ->
                 FilterChip(
                     selected = colorFilterMode == index,
@@ -129,11 +129,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
     }
 
     CheckboxItem(
-        label = stringResource(R.string.pref_grayscale),
+        label = localize(MR.strings.pref_grayscale),
         pref = screenModel.preferences.grayscale(),
     )
     CheckboxItem(
-        label = stringResource(R.string.pref_inverted_colors),
+        label = localize(MR.strings.pref_inverted_colors),
         pref = screenModel.preferences.invertedColors(),
     )
 }

+ 16 - 16
app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt

@@ -5,72 +5,72 @@ import androidx.compose.material3.FilterChip
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.res.stringResource
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.CheckboxItem
 import tachiyomi.presentation.core.components.SettingsChipRow
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 
 private val themes = listOf(
-    R.string.black_background to 1,
-    R.string.gray_background to 2,
-    R.string.white_background to 0,
-    R.string.automatic_background to 3,
+    MR.strings.black_background to 1,
+    MR.strings.gray_background to 2,
+    MR.strings.white_background to 0,
+    MR.strings.automatic_background to 3,
 )
 
 @Composable
 internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
     val readerTheme by screenModel.preferences.readerTheme().collectAsState()
-    SettingsChipRow(R.string.pref_reader_theme) {
+    SettingsChipRow(MR.strings.pref_reader_theme) {
         themes.map { (labelRes, value) ->
             FilterChip(
                 selected = readerTheme == value,
                 onClick = { screenModel.preferences.readerTheme().set(value) },
-                label = { Text(stringResource(labelRes)) },
+                label = { Text(localize(labelRes)) },
             )
         }
     }
 
     CheckboxItem(
-        label = stringResource(R.string.pref_show_page_number),
+        label = localize(MR.strings.pref_show_page_number),
         pref = screenModel.preferences.showPageNumber(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_fullscreen),
+        label = localize(MR.strings.pref_fullscreen),
         pref = screenModel.preferences.fullscreen(),
     )
 
     if (screenModel.hasDisplayCutout) {
         CheckboxItem(
-            label = stringResource(R.string.pref_cutout_short),
+            label = localize(MR.strings.pref_cutout_short),
             pref = screenModel.preferences.cutoutShort(),
         )
     }
 
     CheckboxItem(
-        label = stringResource(R.string.pref_keep_screen_on),
+        label = localize(MR.strings.pref_keep_screen_on),
         pref = screenModel.preferences.keepScreenOn(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_read_with_long_tap),
+        label = localize(MR.strings.pref_read_with_long_tap),
         pref = screenModel.preferences.readWithLongTap(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_always_show_chapter_transition),
+        label = localize(MR.strings.pref_always_show_chapter_transition),
         pref = screenModel.preferences.alwaysShowChapterTransition(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_page_transitions),
+        label = localize(MR.strings.pref_page_transitions),
         pref = screenModel.preferences.pageTransitions(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_flash_page),
+        label = localize(MR.strings.pref_flash_page),
         pref = screenModel.preferences.flashOnPageChange(),
     )
 }

+ 5 - 5
app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt

@@ -11,13 +11,13 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.window.DialogWindowProvider
 import eu.kanade.presentation.components.TabbedDialog
 import eu.kanade.presentation.components.TabbedDialogPaddings
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
 import kotlinx.collections.immutable.persistentListOf
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 
 @Composable
 fun ReaderSettingsDialog(
@@ -27,9 +27,9 @@ fun ReaderSettingsDialog(
     screenModel: ReaderSettingsScreenModel,
 ) {
     val tabTitles = persistentListOf(
-        stringResource(R.string.pref_category_reading_mode),
-        stringResource(R.string.pref_category_general),
-        stringResource(R.string.custom_filter),
+        localize(MR.strings.pref_category_reading_mode),
+        localize(MR.strings.pref_category_general),
+        localize(MR.strings.custom_filter),
     )
     val pagerState = rememberPagerState { tabTitles.size }
 

+ 29 - 29
app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt

@@ -7,45 +7,45 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
-import androidx.compose.ui.res.stringResource
 import eu.kanade.domain.manga.model.readerOrientation
 import eu.kanade.domain.manga.model.readingMode
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
 import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.CheckboxItem
 import tachiyomi.presentation.core.components.HeadingItem
 import tachiyomi.presentation.core.components.SettingsChipRow
 import tachiyomi.presentation.core.components.SliderItem
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.collectAsState
 import java.text.NumberFormat
 
 @Composable
 internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
-    HeadingItem(R.string.pref_category_for_this_series)
+    HeadingItem(MR.strings.pref_category_for_this_series)
     val manga by screenModel.mangaFlow.collectAsState()
 
     val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
-    SettingsChipRow(R.string.pref_category_reading_mode) {
+    SettingsChipRow(MR.strings.pref_category_reading_mode) {
         ReadingMode.entries.map {
             FilterChip(
                 selected = it == readingMode,
                 onClick = { screenModel.onChangeReadingMode(it) },
-                label = { Text(stringResource(it.stringRes)) },
+                label = { Text(localize(it.stringRes)) },
             )
         }
     }
 
     val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
-    SettingsChipRow(R.string.rotation_type) {
+    SettingsChipRow(MR.strings.rotation_type) {
         ReaderOrientation.entries.map {
             FilterChip(
                 selected = it == orientation,
                 onClick = { screenModel.onChangeOrientation(it) },
-                label = { Text(stringResource(it.stringRes)) },
+                label = { Text(localize(it.stringRes)) },
             )
         }
     }
@@ -60,7 +60,7 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
 
 @Composable
 private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
-    HeadingItem(R.string.pager_viewer)
+    HeadingItem(MR.strings.pager_viewer)
 
     val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
     val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
@@ -72,64 +72,64 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
     )
 
     val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
-    SettingsChipRow(R.string.pref_image_scale_type) {
+    SettingsChipRow(MR.strings.pref_image_scale_type) {
         ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
             FilterChip(
                 selected = imageScaleType == index + 1,
                 onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
-                label = { Text(stringResource(it)) },
+                label = { Text(localize(it)) },
             )
         }
     }
 
     val zoomStart by screenModel.preferences.zoomStart().collectAsState()
-    SettingsChipRow(R.string.pref_zoom_start) {
+    SettingsChipRow(MR.strings.pref_zoom_start) {
         ReaderPreferences.ZoomStart.mapIndexed { index, it ->
             FilterChip(
                 selected = zoomStart == index + 1,
                 onClick = { screenModel.preferences.zoomStart().set(index + 1) },
-                label = { Text(stringResource(it)) },
+                label = { Text(localize(it)) },
             )
         }
     }
 
     CheckboxItem(
-        label = stringResource(R.string.pref_crop_borders),
+        label = localize(MR.strings.pref_crop_borders),
         pref = screenModel.preferences.cropBorders(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_landscape_zoom),
+        label = localize(MR.strings.pref_landscape_zoom),
         pref = screenModel.preferences.landscapeZoom(),
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_navigate_pan),
+        label = localize(MR.strings.pref_navigate_pan),
         pref = screenModel.preferences.navigateToPan(),
     )
 
     val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
     CheckboxItem(
-        label = stringResource(R.string.pref_dual_page_split),
+        label = localize(MR.strings.pref_dual_page_split),
         pref = screenModel.preferences.dualPageSplitPaged(),
     )
 
     if (dualPageSplitPaged) {
         CheckboxItem(
-            label = stringResource(R.string.pref_dual_page_invert),
+            label = localize(MR.strings.pref_dual_page_invert),
             pref = screenModel.preferences.dualPageInvertPaged(),
         )
     }
 
     val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
     CheckboxItem(
-        label = stringResource(R.string.pref_page_rotate),
+        label = localize(MR.strings.pref_page_rotate),
         pref = screenModel.preferences.dualPageRotateToFit(),
     )
 
     if (dualPageRotateToFit) {
         CheckboxItem(
-            label = stringResource(R.string.pref_page_rotate_invert),
+            label = localize(MR.strings.pref_page_rotate_invert),
             pref = screenModel.preferences.dualPageRotateToFitInvert(),
         )
     }
@@ -139,7 +139,7 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
 private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenModel) {
     val numberFormat = remember { NumberFormat.getPercentInstance() }
 
-    HeadingItem(R.string.webtoon_viewer)
+    HeadingItem(MR.strings.webtoon_viewer)
 
     val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
     val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
@@ -152,7 +152,7 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
 
     val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
     SliderItem(
-        label = stringResource(R.string.pref_webtoon_side_padding),
+        label = localize(MR.strings.pref_webtoon_side_padding),
         min = ReaderPreferences.WEBTOON_PADDING_MIN,
         max = ReaderPreferences.WEBTOON_PADDING_MAX,
         value = webtoonSidePadding,
@@ -163,25 +163,25 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
     )
 
     CheckboxItem(
-        label = stringResource(R.string.pref_crop_borders),
+        label = localize(MR.strings.pref_crop_borders),
         pref = screenModel.preferences.cropBordersWebtoon(),
     )
 
     val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
     CheckboxItem(
-        label = stringResource(R.string.pref_dual_page_split),
+        label = localize(MR.strings.pref_dual_page_split),
         pref = screenModel.preferences.dualPageSplitWebtoon(),
     )
 
     if (dualPageSplitWebtoon) {
         CheckboxItem(
-            label = stringResource(R.string.pref_dual_page_invert),
+            label = localize(MR.strings.pref_dual_page_invert),
             pref = screenModel.preferences.dualPageInvertWebtoon(),
         )
     }
 
     CheckboxItem(
-        label = stringResource(R.string.pref_double_tap_zoom),
+        label = localize(MR.strings.pref_double_tap_zoom),
         pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
     )
 }
@@ -193,23 +193,23 @@ private fun ColumnScope.TapZonesItems(
     invertMode: ReaderPreferences.TappingInvertMode,
     onSelectInvertMode: (ReaderPreferences.TappingInvertMode) -> Unit,
 ) {
-    SettingsChipRow(R.string.pref_viewer_nav) {
+    SettingsChipRow(MR.strings.pref_viewer_nav) {
         ReaderPreferences.TapZones.mapIndexed { index, it ->
             FilterChip(
                 selected = selected == index,
                 onClick = { onSelect(index) },
-                label = { Text(stringResource(it)) },
+                label = { Text(localize(it)) },
             )
         }
     }
 
     if (selected != 5) {
-        SettingsChipRow(R.string.pref_read_with_tapping_inverted) {
+        SettingsChipRow(MR.strings.pref_read_with_tapping_inverted) {
             ReaderPreferences.TappingInvertMode.entries.map {
                 FilterChip(
                     selected = it == invertMode,
                     onClick = { onSelectInvertMode(it) },
-                    label = { Text(stringResource(it.titleResId)) },
+                    label = { Text(localize(it.titleRes)) },
                 )
             }
         }

+ 12 - 12
app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogHome.kt

@@ -1,6 +1,5 @@
 package eu.kanade.presentation.track
 
-import androidx.annotation.StringRes
 import androidx.compose.animation.animateContentSize
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
@@ -42,20 +41,21 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.tooling.preview.PreviewParameter
 import androidx.compose.ui.unit.dp
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.domain.track.model.toDbTrack
 import eu.kanade.presentation.components.DropdownMenu
 import eu.kanade.presentation.theme.TachiyomiTheme
 import eu.kanade.presentation.track.components.TrackLogoIcon
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.ui.manga.track.TrackItem
 import eu.kanade.tachiyomi.util.system.copyToClipboard
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.localize
 import java.text.DateFormat
 
 private const val UnsetStatusTextAlpha = 0.5F
@@ -131,7 +131,7 @@ fun TrackInfoDialogHome(
 private fun TrackInfoItem(
     title: String,
     tracker: Tracker,
-    @StringRes status: Int?,
+    status: StringResource?,
     onStatusClick: () -> Unit,
     chapters: String,
     onChaptersClick: () -> Unit,
@@ -194,7 +194,7 @@ private fun TrackInfoItem(
                 Row(modifier = Modifier.height(IntrinsicSize.Min)) {
                     TrackDetailsItem(
                         modifier = Modifier.weight(1f),
-                        text = status?.let { stringResource(it) } ?: "",
+                        text = status?.let { localize(it) } ?: "",
                         onClick = onStatusClick,
                     )
                     VerticalDivider()
@@ -209,7 +209,7 @@ private fun TrackInfoItem(
                             modifier = Modifier
                                 .weight(1f)
                                 .alpha(if (score == null) UnsetStatusTextAlpha else 1f),
-                            text = score ?: stringResource(R.string.score),
+                            text = score ?: localize(MR.strings.score),
                             onClick = onScoreClick,
                         )
                     }
@@ -221,14 +221,14 @@ private fun TrackInfoItem(
                         TrackDetailsItem(
                             modifier = Modifier.weight(1F),
                             text = startDate,
-                            placeholder = stringResource(R.string.track_started_reading_date),
+                            placeholder = localize(MR.strings.track_started_reading_date),
                             onClick = onStartDateClick,
                         )
                         VerticalDivider()
                         TrackDetailsItem(
                             modifier = Modifier.weight(1F),
                             text = endDate,
-                            placeholder = stringResource(R.string.track_finished_reading_date),
+                            placeholder = localize(MR.strings.track_finished_reading_date),
                             onClick = onEndDateClick,
                         )
                     }
@@ -279,7 +279,7 @@ private fun TrackInfoItemEmpty(
                 .padding(start = 16.dp)
                 .weight(1f),
         ) {
-            Text(text = stringResource(R.string.add_tracking))
+            Text(text = localize(MR.strings.add_tracking))
         }
     }
 }
@@ -294,7 +294,7 @@ private fun TrackInfoItemMenu(
         IconButton(onClick = { expanded = true }) {
             Icon(
                 imageVector = Icons.Default.MoreVert,
-                contentDescription = stringResource(R.string.label_more),
+                contentDescription = localize(MR.strings.label_more),
             )
         }
         DropdownMenu(
@@ -302,14 +302,14 @@ private fun TrackInfoItemMenu(
             onDismissRequest = { expanded = false },
         ) {
             DropdownMenuItem(
-                text = { Text(stringResource(R.string.action_open_in_browser)) },
+                text = { Text(localize(MR.strings.action_open_in_browser)) },
                 onClick = {
                     onOpenInBrowser()
                     expanded = false
                 },
             )
             DropdownMenuItem(
-                text = { Text(stringResource(R.string.action_remove)) },
+                text = { Text(localize(MR.strings.action_remove)) },
                 onClick = {
                     onRemoved()
                     expanded = false

+ 19 - 18
app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt

@@ -29,18 +29,19 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.PreviewLightDark
 import androidx.compose.ui.unit.dp
+import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.toImmutableList
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.WheelNumberPicker
 import tachiyomi.presentation.core.components.WheelTextPicker
 import tachiyomi.presentation.core.components.material.AlertDialogContent
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.util.isScrolledToEnd
 import tachiyomi.presentation.core.util.isScrolledToStart
 
@@ -48,12 +49,12 @@ import tachiyomi.presentation.core.util.isScrolledToStart
 fun TrackStatusSelector(
     selection: Int,
     onSelectionChange: (Int) -> Unit,
-    selections: Map<Int, Int?>,
+    selections: Map<Int, StringResource?>,
     onConfirm: () -> Unit,
     onDismissRequest: () -> Unit,
 ) {
     BaseSelector(
-        title = stringResource(R.string.status),
+        title = localize(MR.strings.status),
         content = {
             val state = rememberLazyListState()
             ScrollbarLazyColumn(state = state) {
@@ -76,7 +77,7 @@ fun TrackStatusSelector(
                                 onClick = null,
                             )
                             Text(
-                                text = value?.let { stringResource(it) } ?: "",
+                                text = value?.let { localize(it) } ?: "",
                                 style = MaterialTheme.typography.bodyLarge.merge(),
                                 modifier = Modifier.padding(start = 24.dp),
                             )
@@ -101,7 +102,7 @@ fun TrackChapterSelector(
     onDismissRequest: () -> Unit,
 ) {
     BaseSelector(
-        title = stringResource(R.string.chapters),
+        title = localize(MR.strings.chapters),
         content = {
             WheelNumberPicker(
                 items = range.toImmutableList(),
@@ -124,7 +125,7 @@ fun TrackScoreSelector(
     onDismissRequest: () -> Unit,
 ) {
     BaseSelector(
-        title = stringResource(R.string.score),
+        title = localize(MR.strings.score),
         content = {
             WheelTextPicker(
                 items = selections,
@@ -171,15 +172,15 @@ fun TrackDateSelector(
                 ) {
                     if (onRemove != null) {
                         TextButton(onClick = onRemove) {
-                            Text(text = stringResource(R.string.action_remove))
+                            Text(text = localize(MR.strings.action_remove))
                         }
                         Spacer(modifier = Modifier.weight(1f))
                     }
                     TextButton(onClick = onDismissRequest) {
-                        Text(text = stringResource(R.string.action_cancel))
+                        Text(text = localize(MR.strings.action_cancel))
                     }
                     TextButton(onClick = { onConfirm(pickerState.selectedDateMillis!!) }) {
-                        Text(text = stringResource(R.string.action_ok))
+                        Text(text = localize(MR.strings.action_ok))
                     }
                 }
             }
@@ -214,10 +215,10 @@ private fun BaseSelector(
                     Spacer(modifier = Modifier.weight(1f))
                 }
                 TextButton(onClick = onDismissRequest) {
-                    Text(text = stringResource(R.string.action_cancel))
+                    Text(text = localize(MR.strings.action_cancel))
                 }
                 TextButton(onClick = onConfirm) {
-                    Text(text = stringResource(R.string.action_ok))
+                    Text(text = localize(MR.strings.action_ok))
                 }
             }
         },
@@ -234,12 +235,12 @@ private fun TrackStatusSelectorPreviews() {
                 onSelectionChange = {},
                 selections = mapOf(
                     // Anilist values
-                    1 to R.string.reading,
-                    2 to R.string.plan_to_read,
-                    3 to R.string.completed,
-                    4 to R.string.on_hold,
-                    5 to R.string.dropped,
-                    6 to R.string.repeating,
+                    1 to MR.strings.reading,
+                    2 to MR.strings.plan_to_read,
+                    3 to MR.strings.completed,
+                    4 to MR.strings.on_hold,
+                    5 to MR.strings.dropped,
+                    6 to MR.strings.repeating,
                 ),
                 onConfirm = {},
                 onDismissRequest = {},

+ 9 - 10
app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt

@@ -29,7 +29,6 @@ import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.ArrowBack
-import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.filled.CheckCircle
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material3.Button
@@ -50,7 +49,6 @@ import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.capitalize
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.TextFieldValue
@@ -62,11 +60,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.manga.components.MangaCover
 import eu.kanade.presentation.theme.TachiyomiTheme
-import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
+import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.padding
+import tachiyomi.presentation.core.i18n.localize
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.screens.LoadingScreen
 import tachiyomi.presentation.core.util.plus
@@ -121,7 +120,7 @@ fun TrackerSearch(
                             decorationBox = {
                                 if (query.text.isEmpty()) {
                                     Text(
-                                        text = stringResource(R.string.action_search_hint),
+                                        text = localize(MR.strings.action_search_hint),
                                         color = MaterialTheme.colorScheme.onSurfaceVariant,
                                         style = MaterialTheme.typography.bodyLarge,
                                     )
@@ -164,7 +163,7 @@ fun TrackerSearch(
                         .fillMaxWidth(),
                     elevation = ButtonDefaults.elevatedButtonElevation(),
                 ) {
-                    Text(text = stringResource(R.string.action_track))
+                    Text(text = localize(MR.strings.action_track))
                 }
             }
         },
@@ -177,7 +176,7 @@ fun TrackerSearch(
                 if (availableTracks.isEmpty()) {
                     EmptyScreen(
                         modifier = Modifier.padding(innerPadding),
-                        textResource = R.string.no_results_found,
+                        stringRes = MR.strings.no_results_found,
                     )
                 } else {
                     ScrollbarLazyColumn(
@@ -205,7 +204,7 @@ fun TrackerSearch(
                 EmptyScreen(
                     modifier = Modifier.padding(innerPadding),
                     message = queryResult.exceptionOrNull()?.message
-                        ?: stringResource(R.string.unknown_error),
+                        ?: localize(MR.strings.unknown_error),
                 )
             }
         }
@@ -264,19 +263,19 @@ private fun SearchResultItem(
                     )
                     if (type.isNotBlank()) {
                         SearchResultItemDetails(
-                            title = stringResource(R.string.track_type),
+                            title = localize(MR.strings.track_type),
                             text = type,
                         )
                     }
                     if (startDate.isNotBlank()) {
                         SearchResultItemDetails(
-                            title = stringResource(R.string.label_started),
+                            title = localize(MR.strings.label_started),
                             text = startDate,
                         )
                     }
                     if (status.isNotBlank()) {
                         SearchResultItemDetails(
-                            title = stringResource(R.string.track_status),
+                            title = localize(MR.strings.track_status),
                             text = status,
                         )
                     }

Some files were not shown because too many files changed in this diff