Separate screen for OCR languages

This commit is contained in:
Pierre-Yves Nicolas
2026-06-12 16:25:54 +02:00
parent 991ddc8e1d
commit 2ab5dcf7d9
23 changed files with 530 additions and 391 deletions
@@ -69,7 +69,9 @@ import org.fairscan.app.ui.screens.export.ExportEvent
import org.fairscan.app.ui.screens.export.ExportResult
import org.fairscan.app.ui.screens.export.ExportScreenWrapper
import org.fairscan.app.ui.screens.export.ExportViewModel
import org.fairscan.app.ui.screens.settings.OcrLanguagesScreen
import org.fairscan.app.ui.screens.settings.SettingsScreen
import org.fairscan.app.ui.screens.settings.SettingsUiState
import org.fairscan.app.ui.screens.settings.SettingsViewModel
import org.fairscan.app.ui.theme.FairScanTheme
import org.opencv.android.OpenCVLoader
@@ -126,6 +128,7 @@ class MainActivity : ComponentActivity() {
val documentUiState by viewModel.documentUiState.collectAsStateWithLifecycle()
val cropInitialState by viewModel.cropInitState.collectAsStateWithLifecycle()
val exportUiState by exportViewModel.uiState.collectAsStateWithLifecycle()
val settingsUiState by settingsViewModel.uiState.collectAsStateWithLifecycle()
val cameraPermission = rememberCameraPermissionState()
CollectCameraEvents(cameraViewModel, viewModel)
CollectExportEvents(context, exportViewModel)
@@ -237,7 +240,16 @@ class MainActivity : ComponentActivity() {
LibrariesScreen(onBack = navigation.back)
}
is Screen.Overlay.Settings -> {
SettingsScreenWrapper(settingsViewModel, navigation, logger)
SettingsScreenWrapper(settingsUiState, settingsViewModel, navigation, logger)
}
is Screen.Overlay.OcrLanguages -> {
OcrLanguagesScreen(
uiState = settingsUiState,
onBack = navigation.back,
onLanguageClick = settingsViewModel::onLanguageClick,
onRemoveLanguage = settingsViewModel::onRemoveLanguage,
onCancelOcrDownload = settingsViewModel::cancelOcrDownload,
)
}
}
}
@@ -258,6 +270,7 @@ class MainActivity : ComponentActivity() {
@Composable
private fun SettingsScreenWrapper(
settingsUiState: SettingsUiState,
settingsViewModel: SettingsViewModel,
nav: Navigation,
logger: FileLogger,
@@ -277,7 +290,6 @@ class MainActivity : ComponentActivity() {
}
}
}
val settingsUiState by settingsViewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
settingsViewModel.refreshExportDirName()
}
@@ -296,11 +308,7 @@ class MainActivity : ComponentActivity() {
onResetExportDirClick = { settingsViewModel.setExportDirUri(null) },
onExportFormatChanged = { format -> settingsViewModel.setExportFormat(format) },
onExportQualityChanged = { quality -> settingsViewModel.setExportQuality(quality) },
onInstallOcrLanguage = settingsViewModel::installLanguage,
onCancelOcrDownload = settingsViewModel::cancelOcrDownload,
onEnableOcrLanguage = settingsViewModel::setOcrLanguageEnabled,
onDeleteUnusedOcrLanguages = settingsViewModel::deleteUnusedOcrLanguages,
onBack = nav.back,
navigation = nav,
)
}
@@ -484,6 +492,7 @@ class MainActivity : ComponentActivity() {
viewModel.navigateTo(Screen.Overlay.Settings)
}
},
toOcrLanguagesScreen = { viewModel.navigateTo(Screen.Overlay.OcrLanguages) },
back = {
val origin = viewModel.currentScreen.value
viewModel.navigateBack()
@@ -143,13 +143,6 @@ class OcrLanguageRepository(
setLanguageEnabled(code, false)
}
suspend fun deleteInactiveLanguages() {
val enabled = enabledLanguagesValue()
getInstalledLanguages()
.filter { it !in enabled }
.forEach { code -> deleteLanguage(code) }
}
suspend fun buildTesseractLanguageString(): String {
return getActiveLanguages()
.sorted()
@@ -25,6 +25,7 @@ sealed class Screen {
object About : Overlay()
object Libraries : Overlay()
object Settings : Overlay()
object OcrLanguages : Overlay()
}
}
@@ -36,6 +37,7 @@ data class Navigation(
val toAboutScreen: () -> Unit,
val toLibrariesScreen: () -> Unit,
val toSettingsScreen: (() -> Unit)?,
val toOcrLanguagesScreen: () -> Unit,
val back: () -> Unit,
val shouldDisplayBackButton: () -> Boolean,
)
@@ -25,7 +25,7 @@ import org.fairscan.app.ui.state.PageThumbnail
import org.fairscan.imageprocessing.ColorMode
fun dummyNavigation(): Navigation {
return Navigation({}, {}, {}, {}, {}, {}, {}, {}, { -> true})
return Navigation({}, {}, {}, {}, {}, {}, {}, {}, {}, { -> true})
}
fun fakeDocument(pageIds: ImmutableList<String>, context: Context): DocumentUiModel {
@@ -1,212 +0,0 @@
/*
* Copyright 2025-2026 The FairScan authors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.fairscan.app.ui.screens.settings
import android.content.Context
import android.text.format.Formatter
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Download
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.intl.Locale
import androidx.compose.ui.unit.dp
import org.fairscan.app.R
import org.fairscan.app.data.OcrLanguage
@Composable
fun AddLanguageBottomSheetContent(
uiState: SettingsUiState,
onDismiss: () -> Unit,
onInstallLanguage: (String) -> Unit,
) {
val locale = Locale.current.platformLocale
val availableLanguages = remember(uiState.installedOcrLanguages, locale) {
OcrLanguage.AVAILABLE_LANGUAGE_CODES
.filter { it !in uiState.installedOcrLanguages }
.map { code ->
val language = OcrLanguage(code)
language to language.displayName(locale)
}
.sortedBy { (_, name) -> name }
}
val deviceLanguage = locale.displayLanguage
val suggested =
availableLanguages
.map { it.first }
// Comparing language code doesn't work for some languages (e.g. English)
// because we compare a 2-letter code with a 3-letter one (coming from Tesseract).
.firstOrNull { it.locale.displayLanguage == deviceLanguage }
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 8.dp, top = 0.dp)
) {
Text(
text = stringResource(R.string.settings_ocr_download_intro),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.weight(1f)
.padding(0.dp)
)
IconButton(onDismiss) {
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.close))
}
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
if (suggested != null) {
item { TextItem(stringResource(R.string.settings_ocr_suggested)) }
item {
val displayName = suggested.displayName(locale)
LanguageItem(suggested, displayName, onInstallLanguage)
}
item { HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) }
}
item { TextItem(stringResource(R.string.settings_ocr_all_languages)) }
items(
availableLanguages.filter { (lang, _) -> lang != suggested },
key = { (lang, _) -> lang.code }
) { (lang, displayName) ->
LanguageItem(lang, displayName, onInstallLanguage)
}
}
}
@Composable
private fun TextItem(text: String) {
Text(
text = text,
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp)
)
}
@Composable
private fun LanguageItem(
lang: OcrLanguage,
displayName: String,
onInstallLanguage: (String) -> Unit,
) {
ListItem(
headlineContent = { Text(displayName) },
trailingContent = {
Icon(Icons.Default.Download, contentDescription = null)
},
modifier = Modifier.clickable { onInstallLanguage(lang.code) }
)
}
@Composable
fun OcrDownloadDialog(
state: OcrDownloadUiState,
onCancel: () -> Unit,
) {
AlertDialog(
onDismissRequest = {}, // tapping outside the dialog should not cancel
title = {
Text(
stringResource(R.string.settings_ocr_downloading),
style = MaterialTheme.typography.titleMedium
)
},
text = {
if (state.failed) {
Text(
text = stringResource(R.string.settings_ocr_download_error),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
} else {
Column {
Text(state.language.displayName(Locale.current.platformLocale))
Spacer(Modifier.height(16.dp))
val progress =
state.totalBytes?.let { total ->
state.downloadedBytes.toFloat() / total
}
if (progress != null) {
LinearProgressIndicator(progress = { progress }, Modifier.fillMaxWidth())
} else {
LinearProgressIndicator(Modifier.fillMaxWidth())
}
Spacer(Modifier.height(12.dp))
Text(
buildProgressText(
state.downloadedBytes,
state.totalBytes,
LocalContext.current
),
style = MaterialTheme.typography.bodyMedium,
)
}
}
},
dismissButton = {
TextButton(onClick = onCancel) {
Text(stringResource(if (state.failed) R.string.close else R.string.cancel))
}
},
confirmButton = {}
)
}
private fun buildProgressText(
downloadedBytes: Long,
totalBytes: Long?,
context: Context,
): String {
return if (totalBytes != null) {
listOf(
Formatter.formatShortFileSize(context, downloadedBytes),
Formatter.formatShortFileSize(context, totalBytes),
).joinToString(" / ")
} else {
Formatter.formatShortFileSize(context, downloadedBytes)
}
}
@@ -0,0 +1,396 @@
/*
* Copyright 2025-2026 The FairScan authors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.fairscan.app.ui.screens.settings
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
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.text.intl.Locale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.fairscan.app.R
import org.fairscan.app.data.OcrLanguage
import org.fairscan.app.ui.components.BackButton
import org.fairscan.app.ui.theme.FairScanTheme
import android.text.format.Formatter
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.TextButton
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
enum class LanguageState {
ACTIVE,
INACTIVE,
NOT_INSTALLED,
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OcrLanguagesScreen(
uiState: SettingsUiState,
onBack: () -> Unit,
onLanguageClick: (String) -> Unit,
onRemoveLanguage: (String) -> Unit,
onCancelOcrDownload: () -> Unit,
) {
BackHandler { onBack() }
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.settings_ocr_languages)) },
navigationIcon = { BackButton(onBack) },
)
}
) { paddingValues ->
OcrLanguagesContent(
uiState = uiState,
onLanguageClick = onLanguageClick,
onCancelOcrDownload = onCancelOcrDownload,
onRemoveLanguage = onRemoveLanguage,
modifier = Modifier.padding(paddingValues),
)
}
}
@Composable
private fun OcrLanguagesContent(
uiState: SettingsUiState,
onLanguageClick: (String) -> Unit,
onRemoveLanguage: (String) -> Unit,
onCancelOcrDownload: () -> Unit,
modifier: Modifier = Modifier,
) {
val locale = Locale.current.platformLocale
val installed = remember(
uiState.installedOcrLanguages,
uiState.enabledOcrLanguages,
locale,
) {
uiState.installedOcrLanguages
.map { OcrLanguage(it) }
.sortedWith(compareBy { it.displayName(locale) })
}
val available = remember(
uiState.installedOcrLanguages,
locale,
) {
OcrLanguage.AVAILABLE_LANGUAGE_CODES
.filterNot { it in uiState.installedOcrLanguages }
.map { OcrLanguage(it) }
.sortedBy { it.displayName(locale) }
}
val suggested = available.firstOrNull {
it.locale.displayLanguage == locale.displayLanguage
}
LazyColumn(
modifier = modifier.fillMaxSize()
) {
item {
Text(
text = stringResource(R.string.settings_ocr_download_intro),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
if (installed.isNotEmpty()) {
item {
SectionTitle(
stringResource(R.string.settings_ocr_languages_installed)
)
}
items(
items = installed,
key = { it.code }
) { lang ->
LanguageItem(
language = lang,
state = if (lang.code in uiState.enabledOcrLanguages) {
LanguageState.ACTIVE
} else {
LanguageState.INACTIVE
},
onClick = { onLanguageClick(lang.code) },
onRemove = { onRemoveLanguage(lang.code) }
)
}
}
if (suggested != null) {
item {
SectionTitle(
stringResource(R.string.settings_ocr_suggested)
)
}
item {
LanguageItem(
language = suggested,
state = LanguageState.NOT_INSTALLED,
onClick = {
onLanguageClick(suggested.code)
}
)
}
}
item {
SectionTitle(
stringResource(R.string.settings_ocr_languages_available)
)
}
items(
available.filter { it != suggested },
key = { it.code }
) { lang ->
LanguageItem(
language = lang,
state = LanguageState.NOT_INSTALLED,
onClick = {
onLanguageClick(lang.code)
}
)
}
}
uiState.currentDownload?.let { download ->
OcrDownloadDialog(
state = download,
onCancel = onCancelOcrDownload,
)
}
}
@Composable
private fun LanguageItem(
language: OcrLanguage,
state: LanguageState,
onClick: () -> Unit,
onRemove: (() -> Unit)? = null,
) {
var menuExpanded by remember { mutableStateOf(false) }
val locale = Locale.current.platformLocale
ListItem(
headlineContent = {
Text(language.displayName(locale))
},
leadingContent = {
when (state) {
LanguageState.ACTIVE ->
Icon(
Icons.Default.CheckCircle,
contentDescription = null
)
LanguageState.INACTIVE ->
Icon(
Icons.Default.RadioButtonUnchecked,
contentDescription = null
)
LanguageState.NOT_INSTALLED ->
Icon(
Icons.Default.Download,
contentDescription = null
)
}
},
trailingContent = {
if (onRemove != null) {
Box {
IconButton(onClick = { menuExpanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
}
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = { menuExpanded = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.delete)) },
onClick = {
menuExpanded = false
onRemove()
}
)
}
}
}
},
modifier = Modifier.clickable(onClick = onClick)
)
}
@Composable
private fun SectionTitle(
text: String,
) {
Text(
text = text,
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 16.dp,
)
)
}
@Composable
fun OcrDownloadDialog(
state: OcrDownloadUiState,
onCancel: () -> Unit,
) {
AlertDialog(
onDismissRequest = {}, // tapping outside the dialog should not cancel
title = {
Text(
stringResource(R.string.settings_ocr_downloading),
style = MaterialTheme.typography.titleMedium
)
},
text = {
if (state.failed) {
Text(
text = stringResource(R.string.settings_ocr_download_error),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
} else {
Column {
Text(state.language.displayName(Locale.current.platformLocale))
Spacer(Modifier.height(16.dp))
val progress =
state.totalBytes?.let { total ->
state.downloadedBytes.toFloat() / total
}
if (progress != null) {
LinearProgressIndicator(progress = { progress }, Modifier.fillMaxWidth())
} else {
LinearProgressIndicator(Modifier.fillMaxWidth())
}
Spacer(Modifier.height(12.dp))
Text(
buildProgressText(
state.downloadedBytes,
state.totalBytes,
LocalContext.current
),
style = MaterialTheme.typography.bodyMedium,
)
}
}
},
dismissButton = {
TextButton(onClick = onCancel) {
Text(stringResource(if (state.failed) R.string.close else R.string.cancel))
}
},
confirmButton = {}
)
}
private fun buildProgressText(
downloadedBytes: Long,
totalBytes: Long?,
context: Context,
): String {
return if (totalBytes != null) {
listOf(
Formatter.formatShortFileSize(context, downloadedBytes),
Formatter.formatShortFileSize(context, totalBytes),
).joinToString(" / ")
} else {
Formatter.formatShortFileSize(context, downloadedBytes)
}
}
@Preview
@Composable
fun OcrLanguagesScreenPreviewBasic() {
OcrLanguagesScreenPreview(
SettingsUiState(
installedOcrLanguages = setOf("fra", "deu"),
enabledOcrLanguages = setOf("fra")
)
)
}
@Preview
@Composable
fun OcrLanguagesScreenPreviewWithDownloadDialog() {
OcrLanguagesScreenPreview(
SettingsUiState(currentDownload =
OcrDownloadUiState(OcrLanguage("eng"), 500_000, 1_200_000)
)
)
}
@Preview
@Composable
fun OcrLanguagesScreenPreviewWithDownloadError() {
OcrLanguagesScreenPreview(
SettingsUiState(currentDownload =
OcrDownloadUiState(OcrLanguage("eng"), failed = true)
)
)
}
@Composable
fun OcrLanguagesScreenPreview(uiState: SettingsUiState) {
FairScanTheme {
OcrLanguagesScreen(uiState, {}, {}, {}, {})
}
}
@@ -19,33 +19,25 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -54,12 +46,10 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
@@ -69,8 +59,9 @@ import androidx.compose.ui.unit.dp
import org.fairscan.app.R
import org.fairscan.app.data.OcrLanguage
import org.fairscan.app.domain.ExportQuality
import org.fairscan.app.ui.Navigation
import org.fairscan.app.ui.components.BackButton
import org.fairscan.app.ui.components.ConfirmationDialog
import org.fairscan.app.ui.dummyNavigation
import org.fairscan.app.ui.theme.FairScanTheme
@OptIn(ExperimentalMaterial3Api::class)
@@ -82,18 +73,14 @@ fun SettingsScreen(
onResetExportDirClick: () -> Unit,
onExportFormatChanged: (ExportFormat) -> Unit,
onExportQualityChanged: (ExportQuality) -> Unit,
onInstallOcrLanguage: (String) -> Unit,
onCancelOcrDownload: () -> Unit,
onEnableOcrLanguage: (String, Boolean) -> Unit,
onDeleteUnusedOcrLanguages: () -> Unit,
onBack: () -> Unit,
navigation: Navigation,
) {
BackHandler { onBack() }
BackHandler { navigation.back() }
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.settings)) },
navigationIcon = { BackButton(onBack) },
navigationIcon = { BackButton(navigation.back) },
)
}
) { paddingValues ->
@@ -104,10 +91,7 @@ fun SettingsScreen(
onResetExportDirClick,
onExportFormatChanged,
onExportQualityChanged,
onInstallOcrLanguage,
onCancelOcrDownload,
onEnableOcrLanguage,
onDeleteUnusedOcrLanguages,
navigation,
modifier = Modifier.padding(paddingValues),
)
}
@@ -122,14 +106,9 @@ private fun SettingsContent(
onResetExportDirClick: () -> Unit,
onExportFormatChanged: (ExportFormat) -> Unit,
onExportQualityChanged: (ExportQuality) -> Unit,
onInstallOcrLanguage: (String) -> Unit,
onCancelOcrDownload: () -> Unit,
onEnableOcrLanguage: (String, Boolean) -> Unit,
onDeleteUnusedOcrLanguages: () -> Unit,
navigation: Navigation,
modifier: Modifier = Modifier,
) {
var showAddLanguageSheet by remember { mutableStateOf(false) }
val showDeleteLanguageDialog = rememberSaveable { mutableStateOf(false) }
val displayLocale = Locale.current.platformLocale
val export = uiState.export
val (folderLabel, folderLabelColor) = when {
@@ -212,69 +191,24 @@ private fun SettingsContent(
stringResource(R.string.settings_section_ocr),
style = MaterialTheme.typography.titleLarge
)
Spacer(Modifier.height(16.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
uiState.installedOcrLanguages.map { OcrLanguage(it) }
.sortedBy { it.displayName(displayLocale) }
.forEach { lang ->
val code = lang.code
val selected = code in uiState.enabledOcrLanguages
FilterChip(
selected = selected,
onClick = {
onEnableOcrLanguage(code, code !in uiState.enabledOcrLanguages)
},
leadingIcon = { if (selected) Icon(Icons.Default.Check, contentDescription = null) },
label = { Text(lang.displayName(displayLocale)) }
)
}
}
Row (verticalAlignment = Alignment.CenterVertically) {
OutlinedButton(
onClick = { showAddLanguageSheet = true },
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.primary
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary)
) {
Icon(Icons.Default.Add, contentDescription = null)
Spacer(Modifier.width(4.dp))
Text(stringResource(R.string.settings_ocr_add_language))
}
if (uiState.enabledOcrLanguages != uiState.installedOcrLanguages) {
TextButton(onClick = { showDeleteLanguageDialog.value = true }) {
Text(stringResource(R.string.settings_ocr_clean_up))
}
}
}
}
if (showAddLanguageSheet) {
val onDismiss = { showAddLanguageSheet = false }
ModalBottomSheet(onDismissRequest = onDismiss) {
AddLanguageBottomSheetContent(uiState, onDismiss) { code ->
onInstallOcrLanguage(code)
showAddLanguageSheet = false
}
}
}
if (showDeleteLanguageDialog.value) {
val toRemove = uiState.installedOcrLanguages
.filterNot { code -> code in uiState.enabledOcrLanguages}
.joinToString("") { OcrLanguage(it).displayName(displayLocale) }
ConfirmationDialog(
title = "Do you want to remove the following languages?",
message = toRemove,
showDialog = showDeleteLanguageDialog,
onConfirm = onDeleteUnusedOcrLanguages,
)
}
uiState.currentDownload?.let { download ->
OcrDownloadDialog(
state = download,
onCancel = onCancelOcrDownload,
ListItem(
headlineContent = { Text(stringResource(R.string.settings_ocr_languages)) },
supportingContent = {
Text(uiState.enabledOcrLanguages
.map { OcrLanguage(it).displayName(displayLocale) }
.sorted()
.joinToString("")
.ifEmpty { stringResource(R.string.settings_ocr_languages_disabled) }
)
},
trailingContent = {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = null
)
},
modifier = Modifier.clickable { navigation.toOcrLanguagesScreen() }
)
}
}
@@ -402,7 +336,10 @@ fun DirectorySettingItem(
@Preview(heightDp = 780)
@Composable
fun SettingsScreenPreviewWithoutDir() {
SettingsScreenPreview(SettingsUiState(installedOcrLanguages = setOf("fra", "eng")))
SettingsScreenPreview(SettingsUiState(
installedOcrLanguages = setOf("fra", "eng", "deu"),
enabledOcrLanguages = setOf("fra", "eng")
))
}
@Preview
@@ -413,26 +350,6 @@ fun SettingsScreenPreviewWithDir() {
)
}
@Preview
@Composable
fun SettingsScreenPreviewWithDownloadDialog() {
SettingsScreenPreview(
SettingsUiState(currentDownload =
OcrDownloadUiState(OcrLanguage("eng"), 500_000, 1_200_000)
)
)
}
@Preview
@Composable
fun SettingsScreenPreviewWithDownloadError() {
SettingsScreenPreview(
SettingsUiState(currentDownload =
OcrDownloadUiState(OcrLanguage("eng"), failed = true)
)
)
}
@Composable
fun SettingsScreenPreview(uiState: SettingsUiState) {
FairScanTheme {
@@ -443,11 +360,7 @@ fun SettingsScreenPreview(uiState: SettingsUiState) {
onResetExportDirClick = {},
onExportFormatChanged = {},
onExportQualityChanged = {},
onInstallOcrLanguage = {},
onCancelOcrDownload = {},
onEnableOcrLanguage = { _,_->},
onDeleteUnusedOcrLanguages = {},
onBack = {}
navigation = dummyNavigation(),
)
}
}
@@ -137,6 +137,14 @@ class SettingsViewModel(container: AppContainer) : ViewModel() {
}
}
fun onLanguageClick(code: String) {
if (uiState.value.installedOcrLanguages.contains(code)) {
setOcrLanguageEnabled(code, !uiState.value.enabledOcrLanguages.contains(code))
} else {
installLanguage(code)
}
}
fun installLanguage(code: String) {
downloadJob?.cancel()
downloadJob = viewModelScope.launch {
@@ -172,9 +180,9 @@ class SettingsViewModel(container: AppContainer) : ViewModel() {
}
}
fun deleteUnusedOcrLanguages() {
fun onRemoveLanguage(code: String) {
viewModelScope.launch {
ocrLanguageRepo.deleteInactiveLanguages()
ocrLanguageRepo.deleteLanguage(code)
refreshInstalledLanguages()
}
}
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">انسخ السجلات</string>
<string name="creating_export">يجهز التصدير…</string>
<string name="crop">اقتصاص</string>
<string name="delete">حذف</string>
<string name="delete_page">احذف الصفحة</string>
<string name="delete_page_warning">هل تريد حذف هذه الصفحة؟</string>
<string name="developer">مطوّر</string>
@@ -58,12 +59,13 @@
<string name="save">احفظ</string>
<string name="scan_button">مسح جديد</string>
<string name="settings">الإعدادات</string>
<string name="settings_ocr_add_language">إضافة لغة</string>
<string name="settings_ocr_all_languages">جميع اللغات</string>
<string name="settings_ocr_clean_up">تنظيف اللغات غير المستخدمة…</string>
<string name="settings_ocr_download_error">فشل التنزيل. تحقق من اتصالك بالإنترنت.</string>
<string name="settings_ocr_download_intro">يتم تنزيل ملفات اللغة من مشروع Tesseract الرسمي. تبقى عملية التعرّف على النصوص دون اتصال بالإنترنت بالكامل.</string>
<string name="settings_ocr_downloading">جارٍ تنزيل ملف اللغة</string>
<string name="settings_ocr_languages">اللغات</string>
<string name="settings_ocr_languages_available">اللغات المتاحة</string>
<string name="settings_ocr_languages_disabled">معطّل</string>
<string name="settings_ocr_languages_installed">مثبّتة</string>
<string name="settings_ocr_suggested">مقترح</string>
<string name="settings_section_ocr">التعرّف على النصوص</string>
<string name="settings_section_scan">المسح</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Kopírovat protokoly</string>
<string name="creating_export">Příprava exportu…</string>
<string name="crop">Oříznout</string>
<string name="delete">Smazat</string>
<string name="delete_page">Smazat stránku</string>
<string name="delete_page_warning">Chcete smazat tuto stránku?</string>
<string name="developer">Vývojář</string>
@@ -58,12 +59,13 @@
<string name="save">Uložit</string>
<string name="scan_button">Nové skenování</string>
<string name="settings">Nastavení</string>
<string name="settings_ocr_add_language">Přidat jazyk</string>
<string name="settings_ocr_all_languages">Všechny jazyky</string>
<string name="settings_ocr_clean_up">Odstranit nepoužívané jazyky…</string>
<string name="settings_ocr_download_error">Stahování se nezdařilo. Zkontrolujte své internetové připojení.</string>
<string name="settings_ocr_download_intro">Jazykové soubory jsou stahovány z oficiálního projektu Tesseract. Rozpoznávání textu zůstává zcela offline.</string>
<string name="settings_ocr_downloading">Stahování jazykového souboru</string>
<string name="settings_ocr_languages">Jazyky</string>
<string name="settings_ocr_languages_available">Dostupné jazyky</string>
<string name="settings_ocr_languages_disabled">Vypnuto</string>
<string name="settings_ocr_languages_installed">Nainstalované</string>
<string name="settings_ocr_suggested">Doporučeno</string>
<string name="settings_section_ocr">Rozpoznávání textu</string>
<string name="settings_section_scan">Skenování</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Logs kopieren</string>
<string name="creating_export">Export wird vorbereitet…</string>
<string name="crop">Zuschneiden</string>
<string name="delete">Löschen</string>
<string name="delete_page">Seite löschen</string>
<string name="delete_page_warning">Möchten Sie diese Seite löschen?</string>
<string name="developer">Entwickler</string>
@@ -58,12 +59,13 @@
<string name="save">Speichern</string>
<string name="scan_button">Neuer Scan</string>
<string name="settings">Einstellungen</string>
<string name="settings_ocr_add_language">Sprache hinzufügen</string>
<string name="settings_ocr_all_languages">Alle Sprachen</string>
<string name="settings_ocr_clean_up">Nicht verwendete Sprachen bereinigen…</string>
<string name="settings_ocr_download_error">Download fehlgeschlagen. Überprüfen Sie Ihre Internetverbindung.</string>
<string name="settings_ocr_download_intro">Sprachdateien werden vom offiziellen Tesseract-Projekt heruntergeladen. Die Texterkennung bleibt vollständig offline.</string>
<string name="settings_ocr_downloading">Sprachdatei wird heruntergeladen</string>
<string name="settings_ocr_languages">Sprachen</string>
<string name="settings_ocr_languages_available">Verfügbare Sprachen</string>
<string name="settings_ocr_languages_disabled">Deaktiviert</string>
<string name="settings_ocr_languages_installed">Installiert</string>
<string name="settings_ocr_suggested">Empfohlen</string>
<string name="settings_section_ocr">Texterkennung</string>
<string name="settings_section_scan">Scan</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Copiar registros</string>
<string name="creating_export">Preparando la exportación…</string>
<string name="crop">Recortar</string>
<string name="delete">Eliminar</string>
<string name="delete_page">Eliminar página</string>
<string name="delete_page_warning">¿Quieres eliminar esta página?</string>
<string name="developer">Desarrollador</string>
@@ -58,12 +59,13 @@
<string name="save">Guardar</string>
<string name="scan_button">Nuevo escaneo</string>
<string name="settings">Ajustes</string>
<string name="settings_ocr_add_language">Añadir idioma</string>
<string name="settings_ocr_all_languages">Todos los idiomas</string>
<string name="settings_ocr_clean_up">Limpiar idiomas no utilizados…</string>
<string name="settings_ocr_download_error">La descarga ha fallado. Comprueba tu conexión a Internet.</string>
<string name="settings_ocr_download_intro">Los archivos de idioma se descargan desde el proyecto oficial Tesseract. El reconocimiento de texto sigue siendo completamente local.</string>
<string name="settings_ocr_downloading">Descargando archivo de idioma</string>
<string name="settings_ocr_languages">Idiomas</string>
<string name="settings_ocr_languages_available">Idiomas disponibles</string>
<string name="settings_ocr_languages_disabled">Desactivado</string>
<string name="settings_ocr_languages_installed">Instalados</string>
<string name="settings_ocr_suggested">Sugerido</string>
<string name="settings_section_ocr">Reconocimiento de texto</string>
<string name="settings_section_scan">Escaneo</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Copier les logs</string>
<string name="creating_export">Préparation de lexport…</string>
<string name="crop">Recadrer</string>
<string name="delete">Supprimer</string>
<string name="delete_page">Supprimer la page</string>
<string name="delete_page_warning">Voulez-vous supprimer cette page ?</string>
<string name="developer">Développeur</string>
@@ -58,12 +59,13 @@
<string name="save">Enregistrer</string>
<string name="scan_button">Nouveau scan</string>
<string name="settings">Paramètres</string>
<string name="settings_ocr_add_language">Ajouter une langue</string>
<string name="settings_ocr_all_languages">Toutes les langues</string>
<string name="settings_ocr_clean_up">Supprimer les langues inutilisées…</string>
<string name="settings_ocr_download_error">Le téléchargement a échoué. Vérifiez votre connexion Internet.</string>
<string name="settings_ocr_download_intro">Les fichiers de langue sont téléchargés depuis le projet officiel Tesseract. La reconnaissance de texte reste entièrement hors ligne.</string>
<string name="settings_ocr_downloading">Téléchargement du fichier de langue</string>
<string name="settings_ocr_languages">Langues</string>
<string name="settings_ocr_languages_available">Langues disponibles</string>
<string name="settings_ocr_languages_disabled">Désactivé</string>
<string name="settings_ocr_languages_installed">Installées</string>
<string name="settings_ocr_suggested">Suggéré</string>
<string name="settings_section_ocr">Reconnaissance de texte</string>
<string name="settings_section_scan">Scan</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Copiar rexistros</string>
<string name="creating_export">Preparando exportación…</string>
<string name="crop">Recortar</string>
<string name="delete">Eliminar</string>
<string name="delete_page">Eliminar páxina</string>
<string name="delete_page_warning">Queres eliminar esta páxina?</string>
<string name="developer">Desenvolvedor</string>
@@ -58,12 +59,13 @@
<string name="save">Gardar</string>
<string name="scan_button">Novo escaneo</string>
<string name="settings">Configuración</string>
<string name="settings_ocr_add_language">Engadir idioma</string>
<string name="settings_ocr_all_languages">Todos os idiomas</string>
<string name="settings_ocr_clean_up">Limpar idiomas non utilizados…</string>
<string name="settings_ocr_download_error">Produciuse un erro na descarga. Comproba a túa conexión á Internet.</string>
<string name="settings_ocr_download_intro">Os ficheiros de idioma descárganse desde o proxecto oficial Tesseract. O recoñecemento de texto segue sendo completamente sen conexión.</string>
<string name="settings_ocr_downloading">Descargando ficheiro de idioma</string>
<string name="settings_ocr_languages">Idiomas</string>
<string name="settings_ocr_languages_available">Idiomas dispoñibles</string>
<string name="settings_ocr_languages_disabled">Desactivado</string>
<string name="settings_ocr_languages_installed">Instalados</string>
<string name="settings_ocr_suggested">Suxerido</string>
<string name="settings_section_ocr">Recoñecemento de texto</string>
<string name="settings_section_scan">Escaneo</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Copia log</string>
<string name="creating_export">Preparazione dellesportazione…</string>
<string name="crop">Ritaglia</string>
<string name="delete">Elimina</string>
<string name="delete_page">Elimina pagina</string>
<string name="delete_page_warning">Vuoi eliminare questa pagina?</string>
<string name="developer">Sviluppatore</string>
@@ -58,12 +59,13 @@
<string name="save">Salva</string>
<string name="scan_button">Nuova scansione</string>
<string name="settings">Impostazioni</string>
<string name="settings_ocr_add_language">Aggiungi lingua</string>
<string name="settings_ocr_all_languages">Tutte le lingue</string>
<string name="settings_ocr_clean_up">Rimuovi le lingue inutilizzate…</string>
<string name="settings_ocr_download_error">Download non riuscito. Controlla la connessione a Internet.</string>
<string name="settings_ocr_download_intro">I file di lingua vengono scaricati dal progetto ufficiale Tesseract. Il riconoscimento del testo rimane completamente offline.</string>
<string name="settings_ocr_downloading">Download del file di lingua</string>
<string name="settings_ocr_languages">Lingue</string>
<string name="settings_ocr_languages_available">Lingue disponibili</string>
<string name="settings_ocr_languages_disabled">Disattivato</string>
<string name="settings_ocr_languages_installed">Installate</string>
<string name="settings_ocr_suggested">Suggerito</string>
<string name="settings_section_ocr">Riconoscimento del testo</string>
<string name="settings_section_scan">Scansione</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">ログをコピー</string>
<string name="creating_export">エクスポートを準備中…</string>
<string name="crop">トリミング</string>
<string name="delete">削除</string>
<string name="delete_page">ページを削除</string>
<string name="delete_page_warning">このページを削除しますか?</string>
<string name="developer">開発者</string>
@@ -58,12 +59,13 @@
<string name="save">保存</string>
<string name="scan_button">新規スキャン</string>
<string name="settings">設定</string>
<string name="settings_ocr_add_language">言語を追加</string>
<string name="settings_ocr_all_languages">すべての言語</string>
<string name="settings_ocr_clean_up">未使用の言語を整理…</string>
<string name="settings_ocr_download_error">ダウンロードに失敗しました。インターネット接続を確認してください。</string>
<string name="settings_ocr_download_intro">言語ファイルは公式の Tesseract プロジェクトからダウンロードされます。テキスト認識は完全にオフラインで実行されます。</string>
<string name="settings_ocr_downloading">言語ファイルをダウンロード中</string>
<string name="settings_ocr_languages">言語</string>
<string name="settings_ocr_languages_available">利用可能な言語</string>
<string name="settings_ocr_languages_disabled">無効</string>
<string name="settings_ocr_languages_installed">インストール済み</string>
<string name="settings_ocr_suggested">おすすめ</string>
<string name="settings_section_ocr">テキスト認識</string>
<string name="settings_section_scan">スキャン</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Copiar registros</string>
<string name="creating_export">Preparando exportação…</string>
<string name="crop">Recortar</string>
<string name="delete">Excluir</string>
<string name="delete_page">Excluir página</string>
<string name="delete_page_warning">Deseja excluir esta página?</string>
<string name="developer">Desenvolvedor</string>
@@ -58,12 +59,13 @@
<string name="save">Salvar</string>
<string name="scan_button">Nova digitalização</string>
<string name="settings">Configurações</string>
<string name="settings_ocr_add_language">Adicionar idioma</string>
<string name="settings_ocr_all_languages">Todos os idiomas</string>
<string name="settings_ocr_clean_up">Remover idiomas não utilizados…</string>
<string name="settings_ocr_download_error">Falha no download. Verifique sua conexão com a Internet.</string>
<string name="settings_ocr_download_intro">Os arquivos de idioma são baixados do projeto oficial Tesseract. O reconhecimento de texto permanece totalmente offline.</string>
<string name="settings_ocr_downloading">Baixando arquivo de idioma</string>
<string name="settings_ocr_languages">Idiomas</string>
<string name="settings_ocr_languages_available">Idiomas disponíveis</string>
<string name="settings_ocr_languages_disabled">Desativado</string>
<string name="settings_ocr_languages_installed">Instalados</string>
<string name="settings_ocr_suggested">Sugerido</string>
<string name="settings_section_ocr">Reconhecimento de texto</string>
<string name="settings_section_scan">Digitalização</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Копировать журналы</string>
<string name="creating_export">Подготовка экспорта…</string>
<string name="crop">Обрезать</string>
<string name="delete">Удалить</string>
<string name="delete_page">Удалить страницу</string>
<string name="delete_page_warning">Вы желаете удалить эту страницу?</string>
<string name="developer">Разработчик</string>
@@ -58,12 +59,13 @@
<string name="save">Сохранить</string>
<string name="scan_button">Начать</string>
<string name="settings">Настройки</string>
<string name="settings_ocr_add_language">Добавить язык</string>
<string name="settings_ocr_all_languages">Все языки</string>
<string name="settings_ocr_clean_up">Удалить неиспользуемые языки…</string>
<string name="settings_ocr_download_error">Не удалось загрузить файл. Проверьте подключение к Интернету.</string>
<string name="settings_ocr_download_intro">Языковые файлы загружаются из официального проекта Tesseract. Распознавание текста остаётся полностью офлайн.</string>
<string name="settings_ocr_downloading">Загрузка языкового файла</string>
<string name="settings_ocr_languages">Языки</string>
<string name="settings_ocr_languages_available">Доступные языки</string>
<string name="settings_ocr_languages_disabled">Отключено</string>
<string name="settings_ocr_languages_installed">Установленные</string>
<string name="settings_ocr_suggested">Рекомендуется</string>
<string name="settings_section_ocr">Распознавание текста</string>
<string name="settings_section_scan">Сканирование</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Kopiera loggar</string>
<string name="creating_export">Förbereder export…</string>
<string name="crop">Beskär</string>
<string name="delete">Ta bort</string>
<string name="delete_page">Ta bort sida</string>
<string name="delete_page_warning">Vill du ta bort den här sidan?</string>
<string name="developer">Utvecklare</string>
@@ -58,12 +59,13 @@
<string name="save">Spara</string>
<string name="scan_button">Ny skanning</string>
<string name="settings">Inställningar</string>
<string name="settings_ocr_add_language">Lägg till språk</string>
<string name="settings_ocr_all_languages">Alla språk</string>
<string name="settings_ocr_clean_up">Rensa oanvända språk…</string>
<string name="settings_ocr_download_error">Nedladdningen misslyckades. Kontrollera din internetanslutning.</string>
<string name="settings_ocr_download_intro">Språkfiler hämtas från det officiella Tesseract-projektet. Textigenkänning förblir helt offline.</string>
<string name="settings_ocr_downloading">Hämtar språkfil</string>
<string name="settings_ocr_languages">Språk</string>
<string name="settings_ocr_languages_available">Tillgängliga språk</string>
<string name="settings_ocr_languages_disabled">Inaktiverad</string>
<string name="settings_ocr_languages_installed">Installerade</string>
<string name="settings_ocr_suggested">Föreslagen</string>
<string name="settings_section_ocr">Textigenkänning</string>
<string name="settings_section_scan">Skanning</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">Günlükleri kopyala</string>
<string name="creating_export">Dışa aktarma hazırlanıyor…</string>
<string name="crop">Kırp</string>
<string name="delete">Sil</string>
<string name="delete_page">Sayfayı sil</string>
<string name="delete_page_warning">Bu sayfayı silmek istiyor musunuz?</string>
<string name="developer">Geliştirici</string>
@@ -58,12 +59,13 @@
<string name="save">Kaydet</string>
<string name="scan_button">Yeni Tarama</string>
<string name="settings">Ayarlar</string>
<string name="settings_ocr_add_language">Dil ekle</string>
<string name="settings_ocr_all_languages">Tüm diller</string>
<string name="settings_ocr_clean_up">Kullanılmayan dilleri temizle…</string>
<string name="settings_ocr_download_error">İndirme başarısız oldu. İnternet bağlantınızı kontrol edin.</string>
<string name="settings_ocr_download_intro">Dil dosyaları resmi Tesseract projesinden indirilir. Metin tanıma tamamen çevrimdışı olarak çalışır.</string>
<string name="settings_ocr_downloading">Dil dosyası indiriliyor</string>
<string name="settings_ocr_languages">Diller</string>
<string name="settings_ocr_languages_available">Kullanılabilir diller</string>
<string name="settings_ocr_languages_disabled">Devre dışı</string>
<string name="settings_ocr_languages_installed">Yüklü</string>
<string name="settings_ocr_suggested">Önerilen</string>
<string name="settings_section_ocr">Metin tanıma</string>
<string name="settings_section_scan">Tarama</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">複製日誌</string>
<string name="creating_export">正在準備匯出…</string>
<string name="crop">裁切</string>
<string name="delete">刪除</string>
<string name="delete_page">刪除頁面</string>
<string name="delete_page_warning">確定要刪除此頁面嗎?</string>
<string name="developer">開發者</string>
@@ -58,12 +59,13 @@
<string name="save">儲存</string>
<string name="scan_button">開始掃描</string>
<string name="settings">設定</string>
<string name="settings_ocr_add_language">新增語言</string>
<string name="settings_ocr_all_languages">所有語言</string>
<string name="settings_ocr_clean_up">清理未使用的語言…</string>
<string name="settings_ocr_download_error">下載失敗。請檢查您的網路連線。</string>
<string name="settings_ocr_download_intro">語言檔案會從官方 Tesseract 專案下載。文字辨識始終完全離線運作。</string>
<string name="settings_ocr_downloading">正在下載語言檔案</string>
<string name="settings_ocr_languages">語言</string>
<string name="settings_ocr_languages_available">可用語言</string>
<string name="settings_ocr_languages_disabled">已停用</string>
<string name="settings_ocr_languages_installed">已安裝</string>
<string name="settings_ocr_suggested">推薦</string>
<string name="settings_section_ocr">文字辨識</string>
<string name="settings_section_scan">掃描</string>
+5 -3
View File
@@ -20,6 +20,7 @@
<string name="copy_logs">复制日志</string>
<string name="creating_export">正在准备导出…</string>
<string name="crop">裁剪</string>
<string name="delete">删除</string>
<string name="delete_page">删除页面</string>
<string name="delete_page_warning">是否要删除此页面?</string>
<string name="developer">开发者</string>
@@ -58,12 +59,13 @@
<string name="save">保存</string>
<string name="scan_button">新建扫描</string>
<string name="settings">设置</string>
<string name="settings_ocr_add_language">添加语言</string>
<string name="settings_ocr_all_languages">所有语言</string>
<string name="settings_ocr_clean_up">清理未使用的语言…</string>
<string name="settings_ocr_download_error">下载失败。请检查您的网络连接。</string>
<string name="settings_ocr_download_intro">语言文件从官方 Tesseract 项目下载。文本识别始终完全离线运行。</string>
<string name="settings_ocr_downloading">正在下载语言文件</string>
<string name="settings_ocr_languages">语言</string>
<string name="settings_ocr_languages_available">可用语言</string>
<string name="settings_ocr_languages_disabled">已禁用</string>
<string name="settings_ocr_languages_installed">已安装</string>
<string name="settings_ocr_suggested">推荐</string>
<string name="settings_section_ocr">文本识别</string>
<string name="settings_section_scan">扫描</string>
+5 -3
View File
@@ -21,6 +21,7 @@
<string name="copy_logs">Copy logs</string>
<string name="creating_export">Preparing export…</string>
<string name="crop">Crop</string>
<string name="delete">Delete</string>
<string name="delete_page">Delete page</string>
<string name="delete_page_warning">Do you want to delete this page?</string>
<string name="developer">Developer</string>
@@ -62,12 +63,13 @@
<string name="save">Save</string>
<string name="scan_button">New Scan</string>
<string name="settings">Settings</string>
<string name="settings_ocr_add_language">Add language</string>
<string name="settings_ocr_all_languages">All languages</string>
<string name="settings_ocr_clean_up">Clean up unused languages…</string>
<string name="settings_ocr_download_error">Download failed. Check your internet connection.</string>
<string name="settings_ocr_download_intro">"Language files are downloaded from the official Tesseract project. Text recognition remains fully offline."</string>
<string name="settings_ocr_downloading">Downloading language file</string>
<string name="settings_ocr_languages">Languages</string>
<string name="settings_ocr_languages_available">Available languages</string>
<string name="settings_ocr_languages_disabled">Disabled</string>
<string name="settings_ocr_languages_installed">Installed</string>
<string name="settings_ocr_suggested">Suggested</string>
<string name="settings_section_ocr">Text recognition</string>
<string name="settings_section_scan">Scan</string>