Переглянути джерело

Added russian localization

Vadik Sirekanyan 2 роки тому
батько
коміт
9aaeb2252b
23 змінених файлів з 240 додано та 61 видалено
  1. 2 2
      app/src/fdroid/res/values/strings.xml
  2. 10 6
      app/src/main/java/org/sirekanyan/outline/MainContent.kt
  3. 4 2
      app/src/main/java/org/sirekanyan/outline/MainTopAppBar.kt
  4. 11 6
      app/src/main/java/org/sirekanyan/outline/NotSupportedContent.kt
  5. 2 1
      app/src/main/java/org/sirekanyan/outline/ext/Context.kt
  6. 3 2
      app/src/main/java/org/sirekanyan/outline/ext/CoroutineScope.kt
  7. 0 4
      app/src/main/java/org/sirekanyan/outline/ext/Toast.kt
  8. 1 1
      app/src/main/java/org/sirekanyan/outline/feature/keys/KeysErrorContent.kt
  9. 2 1
      app/src/main/java/org/sirekanyan/outline/feature/sort/SortBottomSheet.kt
  10. 6 5
      app/src/main/java/org/sirekanyan/outline/ui/AboutDialog.kt
  11. 3 1
      app/src/main/java/org/sirekanyan/outline/ui/AddKeyButton.kt
  12. 5 4
      app/src/main/java/org/sirekanyan/outline/ui/AddServerContent.kt
  13. 16 5
      app/src/main/java/org/sirekanyan/outline/ui/ConfirmationAlertDialog.kt
  14. 6 4
      app/src/main/java/org/sirekanyan/outline/ui/DialogToolbar.kt
  15. 4 4
      app/src/main/java/org/sirekanyan/outline/ui/DrawerContent.kt
  16. 7 5
      app/src/main/java/org/sirekanyan/outline/ui/KeyBottomSheet.kt
  17. 5 3
      app/src/main/java/org/sirekanyan/outline/ui/RenameContent.kt
  18. 2 1
      app/src/main/java/org/sirekanyan/outline/ui/RenameKeyContent.kt
  19. 2 1
      app/src/main/java/org/sirekanyan/outline/ui/RenameServerContent.kt
  20. 3 1
      app/src/main/java/org/sirekanyan/outline/ui/SearchField.kt
  21. 77 0
      app/src/main/res/values-ru/strings.xml
  22. 67 0
      app/src/main/res/values/strings.xml
  23. 2 2
      app/src/play/res/values/strings.xml

+ 2 - 2
app/src/fdroid/res/values/strings.xml

@@ -1,4 +1,4 @@
 <resources>
-    <string name="outln_source_code_title">GitLab</string>
-    <string name="outln_source_code_link">"https://gitlab.com/sirekanyan.org/outline"</string>
+    <string name="outln_source_code_title" translatable="false">GitLab</string>
+    <string name="outln_source_code_link" translatable="false">"https://gitlab.com/sirekanyan.org/outline"</string>
 </resources>

+ 10 - 6
app/src/main/java/org/sirekanyan/outline/MainContent.kt

@@ -81,7 +81,7 @@ fun MainContent(state: MainState) {
                             TextButton(onClick = { state.dialog = AddServerDialog }) {
                                 Icon(Icons.Default.Add, null)
                                 Spacer(Modifier.size(8.dp))
-                                Text("Add server")
+                                Text(stringResource(R.string.outln_text_add_server))
                             }
                         }
                     }
@@ -98,8 +98,12 @@ fun MainContent(state: MainState) {
                             listOf()
                         } else {
                             listOf(
-                                MenuItem("Sort by…", IconSort) { isSortingVisible = true },
-                                MenuItem("Search", Icons.Default.Search) { search.openSearch() },
+                                MenuItem(R.string.outln_menu_sort, IconSort) {
+                                    isSortingVisible = true
+                                },
+                                MenuItem(R.string.outln_menu_search, Icons.Default.Search) {
+                                    search.openSearch()
+                                },
                             )
                         }
                     MainTopAppBar(
@@ -173,15 +177,15 @@ fun MainContent(state: MainState) {
                     },
                     onMenuClick = state::openDrawer,
                     visibleItems = listOf(
-                        MenuItem("Sort by…", IconSort) {
+                        MenuItem(R.string.outln_menu_sort, IconSort) {
                             isSortingVisible = true
                         },
                     ),
                     overflowItems = listOf(
-                        MenuItem("Edit", Icons.Default.Edit) {
+                        MenuItem(R.string.outln_menu_edit, Icons.Default.Edit) {
                             state.dialog = RenameServerDialog(page.server)
                         },
-                        MenuItem("Delete", Icons.Default.Delete) {
+                        MenuItem(R.string.outln_menu_delete, Icons.Default.Delete) {
                             state.dialog = DeleteServerDialog(page.server)
                         },
                     ),

+ 4 - 2
app/src/main/java/org/sirekanyan/outline/MainTopAppBar.kt

@@ -1,5 +1,6 @@
 package org.sirekanyan.outline
 
+import androidx.annotation.StringRes
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Menu
 import androidx.compose.material.icons.filled.MoreVert
@@ -19,9 +20,10 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 
-data class MenuItem(val text: String, val icon: ImageVector, val onClick: () -> Unit)
+data class MenuItem(@StringRes val text: Int, val icon: ImageVector, val onClick: () -> Unit)
 
 @Composable
 @OptIn(ExperimentalMaterial3Api::class)
@@ -57,7 +59,7 @@ private fun MainMenu(visibleItems: List<MenuItem>, overflowItems: List<MenuItem>
         DropdownMenu(isMenuVisible, { isMenuVisible = false }) {
             overflowItems.forEach { (text, icon, onClick) ->
                 DropdownMenuItem(
-                    text = { Text(text) },
+                    text = { Text(stringResource(text)) },
                     leadingIcon = { Icon(icon, null) },
                     onClick = {
                         isMenuVisible = false

+ 11 - 6
app/src/main/java/org/sirekanyan/outline/NotSupportedContent.kt

@@ -20,27 +20,32 @@ fun NotSupportedContent(onDismissRequest: () -> Unit) {
     val isInstalled = remember { isOutlineInstalled(context) }
     AlertDialog(
         icon = { Icon(Icons.Default.Info, null) },
-        title = { Text("Not supported") },
+        title = { Text(stringResource(R.string.outln_title_not_supported)) },
         text = {
             Text(
-                text = stringResource(R.string.outln_app_name) + " does not support ss:// links. " +
-                        "Would you like to ${if (isInstalled) "open" else "install"} Outline?",
+                text = stringResource(
+                    if (isInstalled) {
+                        R.string.outln_text_not_supported_open
+                    } else {
+                        R.string.outln_text_not_supported_install
+                    }
+                ),
             )
         },
         onDismissRequest = onDismissRequest,
         dismissButton = {
             TextButton(onClick = onDismissRequest) {
-                Text("Cancel")
+                Text(stringResource(R.string.outln_btn_cancel))
             }
         },
         confirmButton = {
             if (isInstalled) {
                 TextButton(onClick = { onDismissRequest(); openOutline(context) }) {
-                    Text("Open Outline")
+                    Text(stringResource(R.string.outln_btn_not_supported_open))
                 }
             } else {
                 TextButton(onClick = { onDismissRequest(); installOutline(context) }) {
-                    Text("Install Outline")
+                    Text(stringResource(R.string.outln_btn_not_supported_install))
                 }
             }
         },

+ 2 - 1
app/src/main/java/org/sirekanyan/outline/ext/Context.kt

@@ -3,12 +3,13 @@ package org.sirekanyan.outline.ext
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
+import org.sirekanyan.outline.R
 
 fun Context.openGooglePlay(uri: String) {
     try {
         startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(uri)))
     } catch (exception: Exception) {
         logDebug("Cannot open Google Play", exception)
-        showToast("Cannot open Google Play")
+        showToast(R.string.outln_toast_cannot_open_play)
     }
 }

+ 3 - 2
app/src/main/java/org/sirekanyan/outline/ext/CoroutineScope.kt

@@ -9,6 +9,7 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.job
 import kotlinx.coroutines.plus
+import org.sirekanyan.outline.R
 import java.net.ConnectException
 import java.net.UnknownHostException
 
@@ -24,10 +25,10 @@ fun rememberStateScope(): CoroutineScope {
             }
             when (throwable) {
                 is UnknownHostException, is ConnectException -> {
-                    context.showToast("Check network connection")
+                    context.showToast(R.string.outln_toast_check_network)
                 }
                 else -> {
-                    context.showToast("Something went wrong")
+                    context.showToast(R.string.outln_toast_something_wrong)
                 }
             }
         }

+ 0 - 4
app/src/main/java/org/sirekanyan/outline/ext/Toast.kt

@@ -4,10 +4,6 @@ import android.content.Context
 import android.widget.Toast
 import androidx.annotation.StringRes
 
-fun Context.showToast(text: String) {
-    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
-}
-
 fun Context.showToast(@StringRes text: Int) {
     Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
 }

+ 1 - 1
app/src/main/java/org/sirekanyan/outline/feature/keys/KeysErrorContent.kt

@@ -29,7 +29,7 @@ fun KeysErrorContent(insets: PaddingValues, onRetry: () -> Unit) {
             textAlign = TextAlign.Center,
         )
         Button(onClick = onRetry) {
-            Text(text = "Try again")
+            Text(text = stringResource(R.string.outln_btn_try_again))
         }
     }
 }

+ 2 - 1
app/src/main/java/org/sirekanyan/outline/feature/sort/SortBottomSheet.kt

@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
 import kotlinx.coroutines.launch
+import org.sirekanyan.outline.R
 import org.sirekanyan.outline.ui.SimpleBottomSheet
 
 @Composable
@@ -25,7 +26,7 @@ fun SortBottomSheet(
 ) {
     val coroutineScope = rememberCoroutineScope()
     SimpleBottomSheet(
-        title = "Sort by…",
+        title = stringResource(R.string.outln_sorting_by),
         onDismissRequest = onDismissRequest,
         items = { sheetState ->
             Sorting.entries.forEach { option ->

+ 6 - 5
app/src/main/java/org/sirekanyan/outline/ui/AboutDialog.kt

@@ -3,6 +3,7 @@ package org.sirekanyan.outline.ui
 import android.content.Intent
 import android.content.Intent.ACTION_SENDTO
 import android.net.Uri
+import androidx.annotation.StringRes
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
@@ -77,7 +78,7 @@ fun AboutDialogContent(onDismiss: () -> Unit) {
         confirmButton = {
             val context = LocalContext.current
             val clipboard = LocalClipboardManager.current
-            AboutItem(Icons.Default.Email, "Send feedback") {
+            AboutItem(Icons.Default.Email, R.string.outln_btn_send_feedback) {
                 val email = "outline@sirekanyan.org"
                 val subject = Uri.encode("Feedback: $appName $appVersion")
                 val intent = Intent(ACTION_SENDTO, Uri.parse("mailto:$email?subject=$subject"))
@@ -86,13 +87,13 @@ fun AboutDialogContent(onDismiss: () -> Unit) {
                 } catch (exception: Exception) {
                     logDebug("Cannot find email app", exception)
                     clipboard.setText(AnnotatedString(email))
-                    context.showToast("Email is copied")
+                    context.showToast(R.string.outln_toast_email_copied)
                 }
             }
             if (isPlayFlavor() || isDebugBuild()) {
                 val packageName = "org.sirekanyan.outline"
                 val playUri = "https://play.google.com/store/apps/details?id=$packageName"
-                AboutItem(IconPlayStore, "Rate on Play Store") {
+                AboutItem(IconPlayStore, R.string.outln_btn_rate_on_play_store) {
                     context.openGooglePlay(playUri)
                     onDismiss()
                 }
@@ -102,7 +103,7 @@ fun AboutDialogContent(onDismiss: () -> Unit) {
 }
 
 @Composable
-private fun AboutItem(icon: ImageVector, text: String, onClick: () -> Unit) {
+private fun AboutItem(icon: ImageVector, @StringRes text: Int, onClick: () -> Unit) {
     TextButton(
         onClick = onClick,
         modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
@@ -110,7 +111,7 @@ private fun AboutItem(icon: ImageVector, text: String, onClick: () -> Unit) {
     ) {
         Icon(icon, null, Modifier.padding(horizontal = 4.dp))
         Text(
-            text = text,
+            text = stringResource(text),
             modifier = Modifier.weight(1f).padding(horizontal = 8.dp),
             maxLines = 1,
             overflow = TextOverflow.Ellipsis,

+ 3 - 1
app/src/main/java/org/sirekanyan/outline/ui/AddKeyButton.kt

@@ -23,7 +23,9 @@ 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.unit.dp
+import org.sirekanyan.outline.R
 
 @Composable
 fun AddKeyButton(isVisible: Boolean, isLoading: Boolean, onClick: () -> Unit) {
@@ -43,7 +45,7 @@ fun AddKeyButton(isVisible: Boolean, isLoading: Boolean, onClick: () -> Unit) {
                     } else {
                         Icon(Icons.Default.Add, null)
                         Spacer(Modifier.width(12.dp))
-                        Text("Add key")
+                        Text(stringResource(R.string.outln_btn_add_key))
                         Spacer(Modifier.width(4.dp))
                     }
                 }

+ 5 - 4
app/src/main/java/org/sirekanyan/outline/ui/AddServerContent.kt

@@ -25,6 +25,7 @@ import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.CoroutineScope
@@ -103,9 +104,9 @@ fun AddServerContent(router: Router) {
     val state = rememberAddServerState(router)
     Column {
         DialogToolbar(
-            title = "Add server",
+            title = R.string.outln_title_add_server,
             onCloseClick = { router.dialog = null },
-            action = "Add" to { state.onAddClicked() },
+            action = R.string.outln_action_add to { state.onAddClicked() },
             isLoading = state.isLoading,
         )
         val focusRequester = remember { FocusRequester() }
@@ -119,7 +120,7 @@ fun AddServerContent(router: Router) {
                 .fillMaxWidth()
                 .padding(16.dp, 24.dp, 16.dp, 8.dp)
                 .focusRequester(focusRequester),
-            label = { Text("Management API URL") },
+            label = { Text(stringResource(R.string.outln_label_server_url)) },
             placeholder = { Text("https://xx.xx.xx.xx:xxx/xxxxx", Modifier.alpha(0.38f)) },
             isError = state.error.isNotEmpty(),
             supportingText = { Text(state.error) },
@@ -136,7 +137,7 @@ fun AddServerContent(router: Router) {
                 onCheckedChange = { state.insecure = it },
             )
             Text(
-                text = "Allow insecure connection",
+                text = stringResource(R.string.outln_label_allow_insecure),
                 modifier = Modifier.padding(end = 16.dp),
                 style = MaterialTheme.typography.bodySmall,
                 color = LocalContentColor.current.copy(alpha = 0.66f),

+ 16 - 5
app/src/main/java/org/sirekanyan/outline/ui/ConfirmationAlertDialog.kt

@@ -8,12 +8,14 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import org.sirekanyan.outline.R
 import org.sirekanyan.outline.api.model.Key
 
 @Composable
 fun DeleteKeyContent(key: Key, onDismiss: () -> Unit, onConfirm: () -> Unit) {
     ConfirmationAlertDialog(
-        text = "Are you sure you want to delete the key named \"${key.nameOrDefault}\"?",
+        text = stringResource(R.string.outln_text_confirm_key, key.nameOrDefault),
         onDismiss = onDismiss,
         onConfirm = onConfirm,
     )
@@ -22,7 +24,7 @@ fun DeleteKeyContent(key: Key, onDismiss: () -> Unit, onConfirm: () -> Unit) {
 @Composable
 fun DeleteServerContent(serverName: String, onDismiss: () -> Unit, onConfirm: () -> Unit) {
     ConfirmationAlertDialog(
-        text = "Are you sure you want to delete the server named \"$serverName\"?",
+        text = stringResource(R.string.outln_text_confirm_server, serverName),
         onDismiss = onDismiss,
         onConfirm = onConfirm,
     )
@@ -32,13 +34,22 @@ fun DeleteServerContent(serverName: String, onDismiss: () -> Unit, onConfirm: ()
 private fun ConfirmationAlertDialog(text: String, onDismiss: () -> Unit, onConfirm: () -> Unit) {
     AlertDialog(
         icon = { Icon(Icons.Default.Delete, null) },
-        title = { Text("Confirmation") },
+        title = { Text(stringResource(R.string.outln_title_confirm)) },
         text = { Text(text) },
         onDismissRequest = onDismiss,
-        dismissButton = { TextButton(onDismiss) { Text("Cancel") } },
+        dismissButton = {
+            TextButton(onClick = onDismiss) {
+                Text(
+                    text = stringResource(R.string.outln_btn_cancel),
+                )
+            }
+        },
         confirmButton = {
             TextButton(onClick = { onConfirm(); onDismiss() }) {
-                Text("Delete", color = MaterialTheme.colorScheme.error)
+                Text(
+                    text = stringResource(R.string.outln_btn_confirm_delete),
+                    color = MaterialTheme.colorScheme.error,
+                )
             }
         },
     )

+ 6 - 4
app/src/main/java/org/sirekanyan/outline/ui/DialogToolbar.kt

@@ -1,5 +1,6 @@
 package org.sirekanyan.outline.ui
 
+import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
@@ -16,19 +17,20 @@ import androidx.compose.material3.TopAppBarDefaults
 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.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 
 @Composable
 @OptIn(ExperimentalMaterial3Api::class)
 fun DialogToolbar(
-    title: String,
+    @StringRes title: Int,
     onCloseClick: () -> Unit,
-    action: Pair<String, () -> Unit>,
+    action: Pair<Int, () -> Unit>,
     isLoading: Boolean,
 ) {
     TopAppBar(
-        title = { Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis) },
+        title = { Text(stringResource(title), maxLines = 1, overflow = TextOverflow.Ellipsis) },
         navigationIcon = {
             IconButton({ onCloseClick() }) { Icon(Icons.Default.Close, null) }
         },
@@ -37,7 +39,7 @@ fun DialogToolbar(
                 CircularProgressIndicator(Modifier.size(56.dp).padding(16.dp), strokeWidth = 2.dp)
             } else {
                 val (actionName, onActionClick) = action
-                TextButton({ onActionClick() }) { Text(actionName) }
+                TextButton({ onActionClick() }) { Text(stringResource(actionName)) }
             }
         },
         colors = TopAppBarDefaults.topAppBarColors(

+ 4 - 4
app/src/main/java/org/sirekanyan/outline/ui/DrawerContent.kt

@@ -118,7 +118,7 @@ private fun DrawerSheetContent(state: MainState, insets: PaddingValues) {
         }
         DrawerItem(
             icon = Icons.Default.Add,
-            label = "Add server",
+            label = stringResource(R.string.outln_drawer_add),
             onClick = {
                 state.dialog = AddServerDialog
             },
@@ -127,7 +127,7 @@ private fun DrawerSheetContent(state: MainState, insets: PaddingValues) {
         if (servers.isNotEmpty()) {
             DrawerItem(
                 icon = Icons.Default.Search,
-                label = "All servers",
+                label = stringResource(R.string.outln_drawer_all),
                 onClick = {
                     state.page = HelloPage
                     state.closeDrawer()
@@ -141,7 +141,7 @@ private fun DrawerSheetContent(state: MainState, insets: PaddingValues) {
             val scope = rememberCoroutineScope()
             DrawerItem(
                 icon = Icons.Default.Warning,
-                label = "Reset database",
+                label = stringResource(R.string.outln_drawer_reset),
                 onClick = {
                     scope.launch(IO) {
                         debugDao.reset()
@@ -151,7 +151,7 @@ private fun DrawerSheetContent(state: MainState, insets: PaddingValues) {
         }
         DrawerItem(
             icon = Icons.Default.Info,
-            label = "About",
+            label = stringResource(R.string.outln_drawer_about),
             onClick = { state.dialog = AboutDialog },
         )
     }

+ 7 - 5
app/src/main/java/org/sirekanyan/outline/ui/KeyBottomSheet.kt

@@ -15,8 +15,10 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.AnnotatedString
 import kotlinx.coroutines.launch
+import org.sirekanyan.outline.R
 import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.ext.showToast
 import org.sirekanyan.outline.ui.icons.IconCopy
@@ -37,11 +39,11 @@ fun KeyBottomSheet(
         onDismissRequest = onDismissRequest,
         items = { sheetState ->
             ListItem(
-                headlineContent = { Text("Copy") },
+                headlineContent = { Text(stringResource(R.string.outln_sheet_copy)) },
                 leadingContent = { Icon(IconCopy, null) },
                 modifier = Modifier.clickable {
                     localClipboard.setText(AnnotatedString(key.accessUrl))
-                    localContext.showToast("Copied")
+                    localContext.showToast(R.string.outln_toast_copied)
                     coroutineScope.launch {
                         sheetState.hide()
                     }.invokeOnCompletion {
@@ -50,7 +52,7 @@ fun KeyBottomSheet(
                 },
             )
             ListItem(
-                headlineContent = { Text("Share") },
+                headlineContent = { Text(stringResource(R.string.outln_sheet_share)) },
                 leadingContent = { Icon(Icons.Default.Share, null) },
                 modifier = Modifier.clickable {
                     coroutineScope.launch {
@@ -66,7 +68,7 @@ fun KeyBottomSheet(
                 },
             )
             ListItem(
-                headlineContent = { Text("Edit") },
+                headlineContent = { Text(stringResource(R.string.outln_sheet_edit)) },
                 leadingContent = { Icon(Icons.Default.Edit, null) },
                 modifier = Modifier.clickable {
                     onEditClick()
@@ -74,7 +76,7 @@ fun KeyBottomSheet(
                 },
             )
             ListItem(
-                headlineContent = { Text("Delete") },
+                headlineContent = { Text(stringResource(R.string.outln_sheet_delete)) },
                 leadingContent = { Icon(Icons.Default.Delete, null) },
                 modifier = Modifier.clickable {
                     onDeleteClick()

+ 5 - 3
app/src/main/java/org/sirekanyan/outline/ui/RenameContent.kt

@@ -1,5 +1,6 @@
 package org.sirekanyan.outline.ui
 
+import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -15,6 +16,7 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
@@ -68,7 +70,7 @@ class RenameState(
 fun RenameContent(
     state: RenameState,
     router: Router,
-    dialogTitle: String,
+    @StringRes dialogTitle: Int,
     initialName: String,
     defaultName: String,
 ) {
@@ -79,7 +81,7 @@ fun RenameContent(
         DialogToolbar(
             title = dialogTitle,
             onCloseClick = { router.dialog = null },
-            action = "Save" to {
+            action = R.string.outln_action_save to {
                 val newName = draft.text.ifBlank { defaultName }
                 state.onSaveClicked(newName)
             },
@@ -96,7 +98,7 @@ fun RenameContent(
                 .fillMaxWidth()
                 .padding(horizontal = 16.dp, vertical = 24.dp)
                 .focusRequester(focusRequester),
-            label = { Text("Name") },
+            label = { Text(stringResource(R.string.outln_label_name)) },
             placeholder = { Text(defaultName) },
             isError = state.error.isNotEmpty(),
             supportingText = { Text(state.error) },

+ 2 - 1
app/src/main/java/org/sirekanyan/outline/ui/RenameKeyContent.kt

@@ -3,6 +3,7 @@ package org.sirekanyan.outline.ui
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import org.sirekanyan.outline.R
 import org.sirekanyan.outline.Router
 import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.app
@@ -33,5 +34,5 @@ private class RenameKeyDelegate(
 fun RenameKeyContent(router: Router, key: Key) {
     val delegate = rememberRenameKeyDelegate(key)
     val state = rememberRenameState(router, delegate)
-    RenameContent(state, router, "Edit key", key.name, key.defaultName)
+    RenameContent(state, router, R.string.outln_title_edit_key, key.name, key.defaultName)
 }

+ 2 - 1
app/src/main/java/org/sirekanyan/outline/ui/RenameServerContent.kt

@@ -3,6 +3,7 @@ package org.sirekanyan.outline.ui
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import org.sirekanyan.outline.R
 import org.sirekanyan.outline.Router
 import org.sirekanyan.outline.SelectedPage
 import org.sirekanyan.outline.api.model.Server
@@ -31,5 +32,5 @@ private class RenameServerDelegate(
 fun RenameServerContent(router: Router, server: Server) {
     val delegate = rememberRenameServerDelegate(router, server)
     val state = rememberRenameState(router, delegate)
-    RenameContent(state, router, "Edit server", server.name, server.getHost())
+    RenameContent(state, router, R.string.outln_title_edit_server, server.name, server.getHost())
 }

+ 3 - 1
app/src/main/java/org/sirekanyan/outline/ui/SearchField.kt

@@ -12,6 +12,8 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.res.stringResource
+import org.sirekanyan.outline.R
 
 @Composable
 fun SearchField(query: String, onQueryChange: (String) -> Unit) {
@@ -26,7 +28,7 @@ fun SearchField(query: String, onQueryChange: (String) -> Unit) {
         singleLine = true,
     )
     if (query.isEmpty()) {
-        Text("Search…", color = contentColor.copy(alpha = 0.38f))
+        Text(stringResource(R.string.outln_hint_search), color = contentColor.copy(alpha = 0.38f))
     }
     LaunchedEffect(Unit) {
         focusRequester.requestFocus()

+ 77 - 0
app/src/main/res/values-ru/strings.xml

@@ -0,0 +1,77 @@
+<resources>
+
+    <!-- Common -->
+    <string name="outln_app_name">Outline Keeper</string>
+    <string name="outln_btn_cancel">Отмена</string>
+    <string name="outln_toast_copied">Скопировано</string>
+    <string name="outln_toast_check_network">Проверьте подключение к сети</string>
+    <string name="outln_toast_something_wrong">Что-то пошло не так</string>
+    <string name="outln_toast_cannot_open_play">Не удалось открыть Google Play</string>
+
+    <!-- Drawer -->
+    <string name="outln_drawer_add">Добавить сервер</string>
+    <string name="outln_drawer_all">Все сервера</string>
+    <string name="outln_drawer_reset">Очистить данные</string>
+    <string name="outln_drawer_about">О приложении</string>
+
+    <!-- Menu -->
+    <string name="outln_menu_sort">Сортировать по…</string>
+    <string name="outln_menu_edit">Изменить</string>
+    <string name="outln_menu_delete">Удалить</string>
+    <string name="outln_menu_search">Поиск</string>
+
+    <!-- Sorting -->
+    <string name="outln_sorting_by">Сортировать по…</string>
+    <string name="outln_sorting_by_id">Id</string>
+    <string name="outln_sorting_by_name">Имя</string>
+    <string name="outln_sorting_by_traffic">Трафик</string>
+
+    <!-- Hello Page -->
+    <string name="outln_hint_search">Поиск…</string>
+    <string name="outln_text_add_server">Добавить сервер</string>
+
+    <!-- Selected Page -->
+    <string name="outln_btn_add_key">Добавить ключ</string>
+    <string name="outln_error_check_network">Проверьте подключение к сети</string>
+    <string name="outln_btn_try_again">Повторить</string>
+
+    <!-- Selected Key -->
+    <string name="outln_sheet_copy">Скопировать</string>
+    <string name="outln_sheet_share">Поделиться</string>
+    <string name="outln_sheet_edit">Изменить</string>
+    <string name="outln_sheet_delete">Удалить</string>
+
+    <!-- Add Server Dialog -->
+    <string name="outln_title_add_server">Добавить сервер</string>
+    <string name="outln_label_server_url">Management API URL</string>
+    <string name="outln_label_allow_insecure">Разрешить небезопасное соединение</string>
+    <string name="outln_action_add">Добавить</string>
+    <string name="outln_error_check_url">Проверьте URL и повторите снова</string>
+    <string name="outln_error_secure_connection">Не удалось установить безопасное соединение</string>
+
+    <!-- Not Supported Dialog -->
+    <string name="outln_title_not_supported">Не поддерживается</string>
+    <string name="outln_text_not_supported_open">Outline Keeper не поддерживает ссылки вида ss://. Хотите открыть приложение Outline?</string>
+    <string name="outln_text_not_supported_install">Outline Keeper не поддерживает ссылки вида ss://. Хотите установить приложение Outline?</string>
+    <string name="outln_btn_not_supported_open">Открыть Outline</string>
+    <string name="outln_btn_not_supported_install">Установить Outline</string>
+
+    <!-- Rename Dialog -->
+    <string name="outln_title_edit_server">Изменить сервер</string>
+    <string name="outln_title_edit_key">Изменить ключ</string>
+    <string name="outln_label_name">Имя</string>
+    <string name="outln_action_save">Сохранить</string>
+    <string name="outln_error_check_name">Проверьте имя и повторите снова</string>
+
+    <!-- Delete Dialog -->
+    <string name="outln_title_confirm">Подтверждение</string>
+    <string name="outln_btn_confirm_delete">Удалить</string>
+    <string name="outln_text_confirm_server">Вы точно хотите удалить сервер с именем «%s»?</string>
+    <string name="outln_text_confirm_key">Вы точно хотите удалить ключ с именем «%s»?</string>
+
+    <!-- About Dialog -->
+    <string name="outln_btn_send_feedback">Обратная связь</string>
+    <string name="outln_btn_rate_on_play_store">Оценить на Play Store</string>
+    <string name="outln_toast_email_copied">E-mail скопирован</string>
+
+</resources>

+ 67 - 0
app/src/main/res/values/strings.xml

@@ -1,10 +1,77 @@
 <resources>
+
+    <!-- Common -->
     <string name="outln_app_name">Outline Keeper</string>
+    <string name="outln_btn_cancel">Cancel</string>
+    <string name="outln_toast_copied">Copied</string>
+    <string name="outln_toast_check_network">Check network connection</string>
+    <string name="outln_toast_something_wrong">Something went wrong</string>
+    <string name="outln_toast_cannot_open_play">Cannot open Google Play</string>
+
+    <!-- Drawer -->
+    <string name="outln_drawer_add">Add server</string>
+    <string name="outln_drawer_all">All servers</string>
+    <string name="outln_drawer_reset">Reset database</string>
+    <string name="outln_drawer_about">About</string>
+
+    <!-- Menu -->
+    <string name="outln_menu_sort">Sort by…</string>
+    <string name="outln_menu_edit">Edit</string>
+    <string name="outln_menu_delete">Delete</string>
+    <string name="outln_menu_search">Search</string>
+
+    <!-- Sorting -->
+    <string name="outln_sorting_by">Sort by…</string>
     <string name="outln_sorting_by_id">Id</string>
     <string name="outln_sorting_by_name">Name</string>
     <string name="outln_sorting_by_traffic">Traffic</string>
+
+    <!-- Hello Page -->
+    <string name="outln_hint_search">Search…</string>
+    <string name="outln_text_add_server">Add server</string>
+
+    <!-- Selected Page -->
+    <string name="outln_btn_add_key">Add key</string>
     <string name="outln_error_check_network">Check your network connection</string>
+    <string name="outln_btn_try_again">Try again</string>
+
+    <!-- Selected Key -->
+    <string name="outln_sheet_copy">Copy</string>
+    <string name="outln_sheet_share">Share</string>
+    <string name="outln_sheet_edit">Edit</string>
+    <string name="outln_sheet_delete">Delete</string>
+
+    <!-- Add Server Dialog -->
+    <string name="outln_title_add_server">Add server</string>
+    <string name="outln_label_server_url">Management API URL</string>
+    <string name="outln_label_allow_insecure">Allow insecure connection</string>
+    <string name="outln_action_add">Add</string>
     <string name="outln_error_check_url">Check URL or try again</string>
     <string name="outln_error_secure_connection">Cannot establish a secure connection</string>
+
+    <!-- Not Supported Dialog -->
+    <string name="outln_title_not_supported">Not supported</string>
+    <string name="outln_text_not_supported_open">Outline Keeper does not support ss:// links. Would you like to open Outline?</string>
+    <string name="outln_text_not_supported_install">Outline Keeper does not support ss:// links. Would you like to install Outline?</string>
+    <string name="outln_btn_not_supported_open">Open Outline</string>
+    <string name="outln_btn_not_supported_install">Install Outline</string>
+
+    <!-- Rename Dialog -->
+    <string name="outln_title_edit_server">Edit server</string>
+    <string name="outln_title_edit_key">Edit key</string>
+    <string name="outln_label_name">Name</string>
+    <string name="outln_action_save">Save</string>
     <string name="outln_error_check_name">Check name or try again</string>
+
+    <!-- Delete Dialog -->
+    <string name="outln_title_confirm">Confirmation</string>
+    <string name="outln_btn_confirm_delete">Delete</string>
+    <string name="outln_text_confirm_server">Are you sure you want to delete the server named \"%s\"?</string>
+    <string name="outln_text_confirm_key">Are you sure you want to delete the key named \"%s\"?</string>
+
+    <!-- About Dialog -->
+    <string name="outln_btn_send_feedback">Send feedback</string>
+    <string name="outln_btn_rate_on_play_store">Rate on Play Store</string>
+    <string name="outln_toast_email_copied">Email is copied</string>
+
 </resources>

+ 2 - 2
app/src/play/res/values/strings.xml

@@ -1,4 +1,4 @@
 <resources>
-    <string name="outln_source_code_title">GitHub</string>
-    <string name="outln_source_code_link">"https://github.com/sirekanian/outline"</string>
+    <string name="outln_source_code_title" translatable="false">GitHub</string>
+    <string name="outln_source_code_link" translatable="false">"https://github.com/sirekanian/outline"</string>
 </resources>