diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/ResourceProvider.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/ResourceProvider.kt new file mode 100644 index 000000000..192486f16 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/ResourceProvider.kt @@ -0,0 +1,16 @@ +package app.omnivore.omnivore.ui + +import android.content.Context +import androidx.annotation.StringRes +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ResourceProvider @Inject constructor( + @ApplicationContext private val context: Context +) { + fun getString(@StringRes stringResId: Int): String { + return context.getString(stringResId) + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt index 9ade3f780..772f03abc 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/AppleAuth.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.remember 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 androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog @@ -28,8 +29,8 @@ fun AppleAuthButton(viewModel: LoginViewModel) { val showDialog = remember { mutableStateOf(false) } LoadingButtonWithIcon( - text = "Continue with Apple", - loadingText = "Signing in...", + text = stringResource(R.string.apple_auth_text), + loadingText = stringResource(R.string.apple_auth_loading), isLoading = viewModel.isLoading, icon = painterResource(id = R.drawable.ic_logo_apple), modifier = Modifier.padding(vertical = 6.dp), diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt index 153d3804e..fa3697a51 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/CreateUserProfile.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.Search import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -25,7 +24,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import app.omnivore.omnivore.R -import org.intellij.lang.annotations.JdkConstants @SuppressLint("CoroutineCreationDuringComposition") @Composable @@ -42,7 +40,7 @@ fun CreateUserProfileView(viewModel: LoginViewModel) { horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "Create Your Profile", + text = stringResource(R.string.create_user_profile_title), style = MaterialTheme.typography.headlineMedium, modifier = Modifier.padding(bottom = 8.dp) ) @@ -61,11 +59,11 @@ fun CreateUserProfileView(viewModel: LoginViewModel) { // TODO: add a activity indicator (maybe after a delay?) if (viewModel.isLoading) { - Text("Loading...") + Text(stringResource(R.string.create_user_profile_loading)) } ClickableText( - text = AnnotatedString("Cancel Sign Up"), + text = AnnotatedString(stringResource(R.string.create_user_profile_action_cancel)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.cancelNewUserSignUp() } @@ -98,8 +96,8 @@ fun UserProfileFields( ) { OutlinedTextField( value = name, - placeholder = { Text(text = "Name") }, - label = { Text(text = "Name") }, + placeholder = { Text(stringResource(R.string.create_user_profile_field_placeholder_name)) }, + label = { Text(stringResource(R.string.create_user_profile_field_label_name)) }, onValueChange = onNameChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) @@ -111,8 +109,8 @@ fun UserProfileFields( ) { OutlinedTextField( value = username, - placeholder = { Text(text = "Username") }, - label = { Text(text = "Username") }, + placeholder = { Text(stringResource(R.string.create_user_profile_field_placeholder_username)) }, + label = { Text(stringResource(R.string.create_user_profile_field_label_username)) }, onValueChange = onUsernameChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), @@ -144,7 +142,7 @@ fun UserProfileFields( } else { Toast.makeText( context, - "Please enter a valid name and username.", + context.getString(R.string.create_user_profile_error_msg), Toast.LENGTH_SHORT ).show() } @@ -154,7 +152,7 @@ fun UserProfileFields( ) ) { Text( - text = "Submit", + text = stringResource(R.string.create_user_profile_action_submit), modifier = Modifier.padding(horizontal = 100.dp) ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt index 87612a4b3..173b00368 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailLogin.kt @@ -1,10 +1,6 @@ package app.omnivore.omnivore.ui.auth import android.annotation.SuppressLint -import android.view.ViewGroup -import android.webkit.CookieManager -import android.webkit.WebView -import android.webkit.WebViewClient import android.widget.Toast import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText @@ -16,10 +12,10 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction @@ -27,8 +23,8 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView import app.omnivore.omnivore.BuildConfig +import app.omnivore.omnivore.R @SuppressLint("CoroutineCreationDuringComposition") @Composable @@ -55,28 +51,28 @@ fun EmailLoginView(viewModel: LoginViewModel) { // TODO: add a activity indicator (maybe after a delay?) if (viewModel.isLoading) { - Text("Loading...") + Text(stringResource(R.string.email_login_loading)) } Column( verticalArrangement = Arrangement.spacedBy(12.dp) ) { ClickableText( - text = AnnotatedString("Return to Social Login"), + text = AnnotatedString(stringResource(R.string.email_login_action_back)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showSocialLogin() } ) ClickableText( - text = AnnotatedString("Don't have an account?"), + text = AnnotatedString(stringResource(R.string.email_login_action_no_account)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showEmailSignUp() } ) ClickableText( - text = AnnotatedString("Forgot your password?"), + text = AnnotatedString(stringResource(R.string.email_login_action_forgot_password)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { @@ -111,8 +107,8 @@ fun LoginFields( ) { OutlinedTextField( value = email, - placeholder = { Text(text = "user@email.com") }, - label = { Text(text = "Email") }, + placeholder = { Text(stringResource(R.string.email_login_field_placeholder_email)) }, + label = { Text(stringResource(R.string.email_login_field_label_email)) }, onValueChange = onEmailChange, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, @@ -123,8 +119,8 @@ fun LoginFields( OutlinedTextField( value = password, - placeholder = { Text(text = "Password") }, - label = { Text(text = "Password") }, + placeholder = { Text(stringResource(R.string.email_login_field_placeholder_password)) }, + label = { Text(stringResource(R.string.email_login_field_label_password)) }, onValueChange = onPasswordChange, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( @@ -141,7 +137,7 @@ fun LoginFields( } else { Toast.makeText( context, - "Please enter an email address and password.", + context.getString(R.string.email_login_error_msg), Toast.LENGTH_SHORT ).show() } @@ -151,7 +147,7 @@ fun LoginFields( ) ) { Text( - text = "Login", + text = stringResource(R.string.email_login_action_login), modifier = Modifier.padding(horizontal = 100.dp) ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt index e9b486184..b2b87d507 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/EmailSignUpView.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction @@ -25,6 +26,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.R @Composable fun EmailSignUpView(viewModel: LoginViewModel) { @@ -32,8 +34,6 @@ fun EmailSignUpView(viewModel: LoginViewModel) { val email = viewModel.pendingEmailUserCreds?.email ?: "" val password = viewModel.pendingEmailUserCreds?.password ?: "" - val verificationMessage = "We've sent a verification email to ${email}. Please verify your email and then tap the button below." - Row( horizontalArrangement = Arrangement.Center ) { @@ -43,7 +43,7 @@ fun EmailSignUpView(viewModel: LoginViewModel) { horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = verificationMessage, + text = stringResource(R.string.email_signup_verification_message, email), style = MaterialTheme.typography.titleMedium ) @@ -55,13 +55,13 @@ fun EmailSignUpView(viewModel: LoginViewModel) { ) ) { Text( - text = "Check Status", + text = stringResource(R.string.email_signup_check_status), modifier = Modifier.padding(horizontal = 100.dp) ) } ClickableText( - text = AnnotatedString("Use a different email?"), + text = AnnotatedString(stringResource(R.string.email_signup_action_use_different_email)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showEmailSignUp() } @@ -115,21 +115,21 @@ fun EmailSignUpForm(viewModel: LoginViewModel) { // TODO: add a activity indicator (maybe after a delay?) if (viewModel.isLoading) { - Text("Loading...") + Text(stringResource(R.string.email_signup_loading)) } Column( verticalArrangement = Arrangement.spacedBy(12.dp) ) { ClickableText( - text = AnnotatedString("Return to Social Login"), + text = AnnotatedString(stringResource(R.string.email_signup_action_back)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showSocialLogin() } ) ClickableText( - text = AnnotatedString("Already have an account?"), + text = AnnotatedString(stringResource(R.string.email_signup_action_already_have_account)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showEmailSignIn() } @@ -167,8 +167,8 @@ fun EmailSignUpFields( ) { OutlinedTextField( value = email, - placeholder = { Text(text = "user@email.com") }, - label = { Text(text = "Email") }, + placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_email)) }, + label = { Text(stringResource(R.string.email_signup_field_label_email)) }, onValueChange = onEmailChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) @@ -176,8 +176,8 @@ fun EmailSignUpFields( OutlinedTextField( value = password, - placeholder = { Text(text = "Password") }, - label = { Text(text = "Password") }, + placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_password)) }, + label = { Text(stringResource(R.string.email_signup_field_label_password)) }, onValueChange = onPasswordChange, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), @@ -186,8 +186,8 @@ fun EmailSignUpFields( OutlinedTextField( value = name, - placeholder = { Text(text = "Name") }, - label = { Text(text = "Name") }, + placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_name)) }, + label = { Text(stringResource(R.string.email_signup_field_label_name)) }, onValueChange = onNameChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) @@ -199,8 +199,8 @@ fun EmailSignUpFields( ) { OutlinedTextField( value = username, - placeholder = { Text(text = "Username") }, - label = { Text(text = "Username") }, + placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_username)) }, + label = { Text(stringResource(R.string.email_signup_field_label_username)) }, onValueChange = onUsernameChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), @@ -231,7 +231,7 @@ fun EmailSignUpFields( } else { Toast.makeText( context, - "Please complete all fields.", + context.getString(R.string.email_signup_error_msg), Toast.LENGTH_SHORT ).show() } @@ -241,7 +241,7 @@ fun EmailSignUpFields( ) ) { Text( - text = "Sign Up", + text = stringResource(R.string.email_signup_action_sign_up), modifier = Modifier.padding(horizontal = 100.dp) ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt index 436bc31c6..46055919d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/GoogleAuth.kt @@ -7,6 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import app.omnivore.omnivore.BuildConfig import app.omnivore.omnivore.R import com.google.android.gms.auth.api.signin.GoogleSignIn @@ -38,8 +39,8 @@ fun GoogleAuthButton(viewModel: LoginViewModel) { } LoadingButtonWithIcon( - text = "Continue with Google", - loadingText = "Signing in...", + text = stringResource(R.string.google_auth_text), + loadingText = stringResource(R.string.google_auth_loading), isLoading = viewModel.isLoading, icon = painterResource(id = R.drawable.ic_logo_google), onClick = { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index e451c2b30..293f8f921 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -11,6 +11,7 @@ import app.omnivore.omnivore.dataService.DataService import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery import app.omnivore.omnivore.networking.Networker import app.omnivore.omnivore.networking.viewer +import app.omnivore.omnivore.ui.ResourceProvider import com.apollographql.apollo3.ApolloClient import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException @@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import java.util.regex.Pattern import javax.inject.Inject + enum class RegistrationState { SocialLogin, EmailSignIn, @@ -40,7 +42,8 @@ class LoginViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository, private val eventTracker: EventTracker, private val networker: Networker, - private val dataService: DataService + private val dataService: DataService, + private val resourceProvider: ResourceProvider ): ViewModel() { private var validateUsernameJob: Job? = null @@ -76,7 +79,7 @@ class LoginViewModel @Inject constructor( datastoreRepo.putString(DatastoreKeys.omnivoreSelfHostedWebServer, webServer) Toast.makeText( context, - "Self-hosting settings updated.", + context.getString(R.string.login_view_model_self_hosting_settings_updated), Toast.LENGTH_SHORT ).show() } @@ -88,7 +91,7 @@ class LoginViewModel @Inject constructor( datastoreRepo.clearValue(DatastoreKeys.omnivoreSelfHostedWebServer) Toast.makeText( context, - "Self-hosting settings reset.", + context.getString(R.string.login_view_model_self_hosting_settings_reset), Toast.LENGTH_SHORT ).show() } @@ -156,7 +159,8 @@ class LoginViewModel @Inject constructor( } if (potentialUsername.length < 4 || potentialUsername.length > 15) { - usernameValidationErrorMessage = "Username must be between 4 and 15 characters long." + usernameValidationErrorMessage = resourceProvider.getString( + R.string.login_view_model_username_validation_length_error_msg) hasValidUsername = false return@launch } @@ -166,7 +170,8 @@ class LoginViewModel @Inject constructor( .matches() if (!isValidPattern) { - usernameValidationErrorMessage = "Username can contain only letters and numbers" + usernameValidationErrorMessage = resourceProvider.getString( + R.string.login_view_model_username_validation_alphanumeric_error_msg) hasValidUsername = false return@launch } @@ -185,11 +190,13 @@ class LoginViewModel @Inject constructor( hasValidUsername = true } else { hasValidUsername = false - usernameValidationErrorMessage = "This username is not available." + usernameValidationErrorMessage = resourceProvider.getString( + R.string.login_view_model_username_not_available_error_msg) } } catch (e: java.lang.Exception) { hasValidUsername = false - usernameValidationErrorMessage = "Sorry we're having trouble connecting to the server." + usernameValidationErrorMessage = resourceProvider.getString( + R.string.login_view_model_connection_error_msg) } } } @@ -216,7 +223,8 @@ class LoginViewModel @Inject constructor( if (result.body()?.authToken != null) { datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) } else { - errorMessage = "Something went wrong. Please check your email/password and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_error_msg) } if (result.body()?.authCookieString != null) { @@ -251,7 +259,8 @@ class LoginViewModel @Inject constructor( isLoading = false if (result.errorBody() != null) { - errorMessage = "Something went wrong. Please check your entries and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_two_error_msg) } else { pendingEmailUserCreds = PendingEmailUserCreds(email, password) } @@ -284,7 +293,8 @@ class LoginViewModel @Inject constructor( if (result.body()?.authToken != null) { datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!) } else { - errorMessage = "Something went wrong. Please check your email/password and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_error_msg) } if (result.body()?.authCookieString != null) { @@ -315,7 +325,7 @@ class LoginViewModel @Inject constructor( } fun showGoogleErrorMessage() { - errorMessage = "Failed to authenticate with Google." + errorMessage = resourceProvider.getString(R.string.login_view_model_google_auth_error_msg) } fun handleGoogleAuthTask(task: Task) { @@ -324,7 +334,8 @@ class LoginViewModel @Inject constructor( // If token is missing then set the error message if (googleIdToken == null) { - errorMessage = "No authentication token found." + errorMessage = resourceProvider.getString( + R.string.login_view_model_missing_auth_token_error_msg) return } @@ -361,10 +372,12 @@ class LoginViewModel @Inject constructor( } 418 -> { // Show pending email state - errorMessage = "Something went wrong. Please check your credentials and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_two_error_msg) } else -> { - errorMessage = "Something went wrong. Please check your credentials and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_two_error_msg) } } } @@ -386,7 +399,8 @@ class LoginViewModel @Inject constructor( ) registrationStateLiveData.value = RegistrationState.PendingUser } else { - errorMessage = "Something went wrong. Please check your credentials and try again" + errorMessage = resourceProvider.getString( + R.string.login_view_model_something_went_wrong_two_error_msg) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/SelfHostedView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/SelfHostedView.kt index 8df9fe148..bc1842fb3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/SelfHostedView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/SelfHostedView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction @@ -34,6 +35,7 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import app.omnivore.omnivore.BuildConfig import app.omnivore.omnivore.DatastoreKeys +import app.omnivore.omnivore.R @SuppressLint("CoroutineCreationDuringComposition") @Composable @@ -62,7 +64,7 @@ fun SelfHostedView(viewModel: LoginViewModel) { // TODO: add a activity indicator (maybe after a delay?) if (viewModel.isLoading) { - Text("Loading...") + Text(stringResource(R.string.self_hosted_view_loading)) } Row( @@ -72,14 +74,14 @@ fun SelfHostedView(viewModel: LoginViewModel) { verticalArrangement = Arrangement.spacedBy(12.dp) ) { ClickableText( - text = AnnotatedString("Reset"), + text = AnnotatedString(stringResource(R.string.self_hosted_view_action_reset)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.resetSelfHostingDetails(context) }, modifier = Modifier.align(Alignment.CenterHorizontally) ) ClickableText( - text = AnnotatedString("Back"), + text = AnnotatedString(stringResource(R.string.self_hosted_view_action_back)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showSocialLogin() }, @@ -91,7 +93,7 @@ fun SelfHostedView(viewModel: LoginViewModel) { // "your private self-hosted instance.\n\n" // ) ClickableText( - text = AnnotatedString("Learn more about self-hosting Omnivore"), + text = AnnotatedString(stringResource(R.string.self_hosted_view_action_learn_more)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { @@ -130,7 +132,7 @@ fun SelfHostedFields( OutlinedTextField( value = apiServer, placeholder = { Text(text = "https://api-prod.omnivore.app/") }, - label = { Text(text = "API Server") }, + label = { Text(stringResource(R.string.self_hosted_view_field_api_url_label)) }, onValueChange = onAPIServerChange, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, @@ -142,7 +144,7 @@ fun SelfHostedFields( OutlinedTextField( value = webServer, placeholder = { Text(text = "https://omnivore.app/") }, - label = { Text(text = "Web Server") }, + label = { Text(stringResource(R.string.self_hosted_view_field_web_url_label)) }, onValueChange = onWebServerChange, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, @@ -158,7 +160,7 @@ fun SelfHostedFields( } else { Toast.makeText( context, - "Please enter API Server and Web server addresses.", + context.getString(R.string.self_hosted_view_error_msg), Toast.LENGTH_SHORT ).show() } @@ -168,7 +170,7 @@ fun SelfHostedFields( ) ) { Text( - text = "Save", + text = stringResource(R.string.self_hosted_view_action_save), modifier = Modifier.padding(horizontal = 100.dp) ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt index efe3bfe75..6d1e2e138 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -135,7 +135,7 @@ fun AuthProviderView(viewModel: LoginViewModel) { AppleAuthButton(viewModel) ClickableText( - text = AnnotatedString("Continue with Email"), + text = AnnotatedString(stringResource(R.string.welcome_screen_action_continue_with_email)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showEmailSignIn() } @@ -144,7 +144,7 @@ fun AuthProviderView(viewModel: LoginViewModel) { Spacer(modifier = Modifier.weight(1.0F)) ClickableText( - text = AnnotatedString("Self-hosting options"), + text = AnnotatedString(stringResource(R.string.welcome_screen_action_self_hosting_options)), style = MaterialTheme.typography.titleMedium .plus(TextStyle(textDecoration = TextDecoration.Underline)), onClick = { viewModel.showSelfHostedSettings() }, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelCreationDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelCreationDialog.kt index 7bc4c9ab8..0facbd226 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelCreationDialog.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelCreationDialog.kt @@ -20,10 +20,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import app.omnivore.omnivore.R @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -59,13 +61,13 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit) .fillMaxWidth() ) { TextButton(onClick = onDismiss) { - Text(text = "Cancel") + Text(text = stringResource(R.string.label_creation_action_cancel)) } - Text("Create New Label", fontWeight = FontWeight.ExtraBold) + Text(stringResource(R.string.label_creation_title), fontWeight = FontWeight.ExtraBold) TextButton(onClick = { onSave(labelName, selectedHex) }) { - Text(text = "Create") + Text(text = stringResource(R.string.label_creation_action_create)) } } @@ -76,7 +78,7 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit) .fillMaxWidth() .padding(vertical = 10.dp) ) { - Text("Assign a name and color.") + Text(stringResource(R.string.label_creation_content)) } Row( @@ -88,7 +90,7 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit) ) { OutlinedTextField( value = labelName, - placeholder = { Text(text = "Label Name") }, + placeholder = { Text(stringResource(R.string.label_creation_label_placeholder)) }, onValueChange = { labelName = it }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt index 7035d667a..f84a73d86 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.* +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue @@ -44,6 +45,7 @@ import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.R import app.omnivore.omnivore.models.ServerSyncStatus import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.ui.library.LibraryViewModel @@ -260,7 +262,9 @@ fun LabelsSelectionSheetContent( it.name.toLowerCase(Locale.current) == text } - val titleText = if (isLibraryMode) "Filter by Label" else "Set Labels" + val titleText = if (isLibraryMode) + stringResource(R.string.label_selection_sheet_title) else + stringResource(R.string.label_selection_sheet_title_alt) Surface( modifier = Modifier @@ -282,13 +286,15 @@ fun LabelsSelectionSheetContent( .fillMaxWidth() ) { TextButton(onClick = onCancel) { - Text(text = "Cancel") + Text(text = stringResource(R.string.label_selection_sheet_action_cancel)) } Text(titleText, fontWeight = FontWeight.ExtraBold) TextButton(onClick = { onSave(state.chips.map { it.label }) }) { - Text(text = if (isLibraryMode) "Search" else "Save") + Text(text = if (isLibraryMode) + stringResource(R.string.label_selection_sheet_action_search) else + stringResource(R.string.label_selection_sheet_action_save)) } } @@ -341,7 +347,11 @@ fun LabelsSelectionSheetContent( modifier = Modifier .fillMaxWidth() .clickable { - val label = findOrCreateLabel(labelsViewModel = labelsViewModel, labels = labels, name = filterTextValue) + val label = findOrCreateLabel( + labelsViewModel = labelsViewModel, + labels = labels, + name = filterTextValue + ) state.addChip(LabelChipView(label)) filterTextValue = TextFieldValue() } @@ -354,7 +364,7 @@ fun LabelsSelectionSheetContent( contentDescription = null, modifier = Modifier.padding(end = 8.dp) ) - Text(text = "Create a new label named \"${filterTextValue.text}\"") + Text(text = stringResource(R.string.label_selection_sheet_text_create, filterTextValue.text)) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt index 0bb2d40d3..c6e587893 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.unit.dp @@ -69,7 +70,7 @@ fun LibraryFilterBar(viewModel: LibraryViewModel) { ) AssistChip( onClick = { viewModel.showLabelsSelectionSheetLiveData.value = true }, - label = { Text("Labels") }, + label = { Text(stringResource(R.string.library_filter_bar_label_labels)) }, trailingIcon = { Icon( Icons.Default.ArrowDropDown, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt index 19e2a6bb8..14df87883 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.navigation.NavHostController import app.omnivore.omnivore.R @@ -34,7 +35,9 @@ fun LibraryNavigationBar( TopAppBar( title = { - Text(if (actionsMenuItem == null) "Library" else "") + Text(if (actionsMenuItem == null) + stringResource(R.string.library_nav_bar_title) else + stringResource(R.string.library_nav_bar_title_alt)) }, modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.topAppBarColors( @@ -218,7 +221,7 @@ fun SearchField( value = searchText, onValueChange = onSearchTextChanged, placeholder = { - Text(text = "Search") + Text(text = stringResource(R.string.library_nav_bar_field_placeholder_search)) }, leadingIcon = { IconButton( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index d171f7d1b..632b32950 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -19,6 +19,7 @@ import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput import app.omnivore.omnivore.models.ServerSyncStatus import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.* +import app.omnivore.omnivore.ui.ResourceProvider import com.apollographql.apollo3.api.Optional import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull import dagger.hilt.android.lifecycle.HiltViewModel @@ -31,7 +32,8 @@ import javax.inject.Inject class LibraryViewModel @Inject constructor( private val networker: Networker, private val dataService: DataService, - private val datastoreRepo: DatastoreRepository + private val datastoreRepo: DatastoreRepository, + private val resourceProvider: ResourceProvider ): ViewModel(), SavedItemViewModel { private val contentRequestChannel = Channel(capacity = Channel.UNLIMITED) @@ -348,9 +350,9 @@ class LibraryViewModel @Inject constructor( dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) if (!networkResult || labelCreationError) { - snackbarMessage = "Unable to set labels" + snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_error) } else { - snackbarMessage = "Labels updated" + snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_success) } CoroutineScope(Dispatchers.Main).launch { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index 147c4d69e..61a46e0db 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -43,6 +43,7 @@ import kotlinx.coroutines.launch import app.omnivore.omnivore.persistence.entities.Highlight import app.omnivore.omnivore.ui.theme.OmnivoreTheme import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.res.stringResource fun notebookMD(notes: List, highlights: List): String { @@ -80,6 +81,9 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote: val scrollState = rememberScrollState() val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } + val context = LocalContext.current + val snackBarHostStateMsg = context.getString(R.string.notebook_view_snackbar_msg) + val clipboard: ClipboardManager? = LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? @@ -89,7 +93,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote: Scaffold( topBar = { TopAppBar( - title = { Text("Notebook") }, + title = { Text(stringResource(R.string.notebook_view_title)) }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background ), @@ -109,7 +113,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote: onDismissRequest = { isMenuOpen = false } ) { DropdownMenuItem( - text = { Text("Copy") }, + text = { Text(stringResource(R.string.notebook_view_action_copy)) }, onClick = { val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights)) clipboard?.let { @@ -117,7 +121,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote: } ?: run { coroutineScope.launch { snackBarHostState - .showSnackbar("Notebook copied") + .showSnackbar(snackBarHostStateMsg) } } isMenuOpen = false @@ -154,7 +158,7 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String Scaffold( topBar = { CenterAlignedTopAppBar( - title = { Text("Note") }, + title = { Text(stringResource(R.string.edit_note_modal_title)) }, modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background @@ -163,14 +167,14 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String TextButton(onClick = { onDismiss(false, initialValue) }) { - Text(text = "Cancel") + Text(text = stringResource(R.string.edit_note_modal_action_cancel)) } }, actions = { TextButton(onClick = { onDismiss(true, annotation.value) }) { - Text(text = "Save") + Text(text = stringResource(R.string.edit_note_modal_action_save)) } } ) @@ -205,7 +209,7 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl .padding(start = 15.dp) .padding(top = 20.dp, bottom = 50.dp) ) { - Text("Article Notes") + Text(stringResource(R.string.article_notes_title)) Divider(modifier = Modifier.padding(bottom= 15.dp)) notes.forEach { note -> MarkdownText( @@ -231,7 +235,7 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl ) ) { Text( - text = "Add Notes...", + text = stringResource(R.string.article_notes_action_add_notes), style = androidx.compose.material.MaterialTheme.typography.subtitle2, modifier = Modifier .padding(vertical = 2.dp, horizontal = 0.dp), @@ -250,6 +254,8 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } + val context = LocalContext.current + val snackBarHostStateMsg = context.getString(R.string.highlights_list_snackbar_msg) val clipboard: ClipboardManager? = LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? @@ -258,7 +264,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi .padding(start = 15.dp) .padding(top = 40.dp, bottom = 100.dp) ) { - Text("Highlights") + Text(stringResource(R.string.highlights_list_title)) Divider(modifier = Modifier.padding(bottom= 10.dp)) highlights.forEach { highlight -> var isMenuOpen by remember { mutableStateOf(false) } @@ -282,7 +288,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi onDismissRequest = { isMenuOpen = false } ) { DropdownMenuItem( - text = { Text("Copy") }, + text = { Text(stringResource(R.string.highlights_list_action_copy)) }, onClick = { val clip = ClipData.newPlainText("highlight", highlight.quote) clipboard?.let { @@ -290,7 +296,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi } ?: run { coroutineScope.launch { snackBarHostState - .showSnackbar("Highlight copied") + .showSnackbar(snackBarHostStateMsg) } } isMenuOpen = false @@ -354,7 +360,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi ) ) { Text( - text = "Add Note...", + text = stringResource(R.string.highlights_list_action_add_note), style = androidx.compose.material.MaterialTheme.typography.subtitle2, modifier = Modifier .padding(vertical = 2.dp, horizontal = 0.dp), @@ -365,7 +371,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi } if (highlights.isEmpty()) { Text( - text = "You have not added any highlights to this page.", + text = stringResource(R.string.highlights_list_error_msg_no_highlights), style = androidx.compose.material.MaterialTheme.typography.subtitle2, modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp) ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt index 004671c42..782dc2c8f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -103,7 +104,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { } } - Text("Font Size:", style = TextStyle( + Text(stringResource(R.string.reader_preferences_view_font_size), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, color = Color(red = 137, green = 137, blue = 137) @@ -118,7 +119,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { valueRange = 10f..48f, ) - Text("Margin", style = TextStyle( + Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, color = Color(red = 137, green = 137, blue = 137) @@ -133,7 +134,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { valueRange = 60f..100f, ) - Text("Line Spacing", style = TextStyle( + Text(stringResource(R.string.reader_preferences_view_line_spacing), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, color = Color(red = 137, green = 137, blue = 137) @@ -153,13 +154,13 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { modifier = Modifier .padding(vertical = 4.dp) ) { - Text("Theme:", style = TextStyle( + Text(stringResource(R.string.reader_preferences_view_theme), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, color = Color(red = 137, green = 137, blue = 137) )) Spacer(modifier = Modifier.weight(1.0F)) - Text("Auto", style = TextStyle( + Text(stringResource(R.string.reader_preferences_view_auto), style = TextStyle( fontSize = 10.sp, fontWeight = FontWeight.Normal, color = Color(red = 137, green = 137, blue = 137) @@ -208,7 +209,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { } Row(verticalAlignment = Alignment.CenterVertically) { - Text("High Contrast Text", + Text( + stringResource(R.string.reader_preferences_view_high_constrast_text), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, @@ -225,7 +227,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { } Row(verticalAlignment = Alignment.CenterVertically) { - Text("Justify Text", + Text( + stringResource(R.string.reader_preferences_view_justify_text), style = TextStyle( fontSize = 15.sp, fontWeight = FontWeight.Normal, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index e2e12741c..e680a42a7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -44,6 +44,7 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt import androidx.compose.material3.Button import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import app.omnivore.omnivore.ui.components.LabelsViewModel import app.omnivore.omnivore.ui.notebook.EditNoteModal @@ -80,7 +81,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { .background(color = Color.Black) ) { if (viewModel.hasFetchError.value == true) { - Text("We were unable to fetch your content.") + Text(stringResource(R.string.web_reader_loading_container_error_msg)) } else { WebReaderLoadingContainer( requestID = requestID, @@ -200,13 +201,13 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, sheetContent = { when (bottomSheetState) { BottomSheetState.PREFERENCES -> { - BottomSheetUI("Reader Preferences") { + BottomSheetUI(stringResource(R.string.web_reader_loading_container_bottom_sheet_reader_preferences)) { ReaderPreferencesView(webReaderViewModel) } } BottomSheetState.NOTEBOOK -> { webReaderParams?.let { params -> - BottomSheetUI(title = "Notebook") { + BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_notebook)) { NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel, onEditNote = { notebookViewModel.highlightUnderEdit = it webReaderViewModel.setBottomSheet(BottomSheetState.EDITNOTE) @@ -255,7 +256,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, ) } BottomSheetState.LABELS -> { - BottomSheetUI(title = "Notebook") { + BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_notebook)) { LabelsSelectionSheetContent( labels = labels, labelsViewModel = labelsViewModel, @@ -283,7 +284,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } BottomSheetState.LINK -> { - BottomSheetUI(title = "Open Link") { + BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_open_link)) { OpenLinkView(webReaderViewModel) } } @@ -460,25 +461,25 @@ fun OpenLinkView(webReaderViewModel: WebReaderViewModel) { .padding(horizontal = 50.dp), verticalArrangement = Arrangement.spacedBy(20.dp)) { Row { Button(onClick = { webReaderViewModel.openCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) { - Text(text = "Open in Browser") + Text(text = stringResource(R.string.open_link_view_action_open_in_browser)) } } Row() { Button(onClick = { webReaderViewModel.saveCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) { - Text(text = "Save to Omnivore") + Text(text = stringResource(R.string.open_link_view_action_save_to_omnivore)) } } Row() { Button(onClick = {webReaderViewModel.copyCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) { - Text(text = "Copy Link") + Text(text = stringResource(R.string.open_link_view_action_copy_link)) } } Row { Button(onClick = {webReaderViewModel.resetBottomSheet() }, modifier = Modifier.fillMaxWidth()) { - Text(text = "Cancel") + Text(text = stringResource(R.string.open_link_view_action_cancel)) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt index a32e3fcc2..0f80b5689 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.* import app.omnivore.omnivore.DatastoreKeys import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.EventTracker +import app.omnivore.omnivore.R import app.omnivore.omnivore.dataService.* import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput @@ -139,7 +140,11 @@ class WebReaderViewModel @Inject constructor( currentLink?.let { viewModelScope.launch { val success = networker.saveUrl(it) - Toast.makeText(context, if (success) "Link saved" else "Error saving link" , Toast.LENGTH_SHORT).show() + Toast.makeText(context, + if (success) + context.getString(R.string.web_reader_view_model_save_link_success) else + context.getString(R.string.web_reader_view_model_save_link_error), + Toast.LENGTH_SHORT).show() } } bottomSheetStateLiveData.postValue(BottomSheetState.NONE) @@ -153,7 +158,9 @@ class WebReaderViewModel @Inject constructor( clipboard.setPrimaryClip(clip) clipboard?.let { clipboard?.setPrimaryClip(clip) - Toast.makeText(context, "Link Copied", Toast.LENGTH_SHORT).show() + Toast.makeText(context, + context.getString(R.string.web_reader_view_model_copy_link_success), + Toast.LENGTH_SHORT).show() } } bottomSheetStateLiveData.postValue(BottomSheetState.NONE) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt index cb1a20186..5ef543e26 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt @@ -14,8 +14,10 @@ import androidx.compose.material.ButtonDefaults import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.omnivore.omnivore.MainActivity +import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.reader.PDFReaderActivity import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity import kotlinx.coroutines.launch @@ -36,7 +38,7 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee .fillMaxSize() .padding(top = 48.dp, bottom = 32.dp) ) { - Text(text = viewModel.message ?: "Saving") + Text(text = viewModel.message ?: stringResource(R.string.save_content_msg)) Row { if (enableReadNow) { Button( @@ -55,7 +57,7 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee backgroundColor = Color.White ) ) { - Text(text = "Read Now") + Text(text = stringResource(R.string.save_content_action_read_now)) } Spacer(modifier = Modifier.width(8.dp)) @@ -72,7 +74,9 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee backgroundColor = Color(0xffffd234) ) ) { - Text(text = if (enableReadNow) "Read Later" else "Dismiss") + Text(text = if (enableReadNow) + stringResource(R.string.save_content_action_read_later) else + stringResource(R.string.save_content_action_dismiss)) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt index 5877d8b03..6bb28a50c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt @@ -12,8 +12,10 @@ import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.Constants import app.omnivore.omnivore.DatastoreKeys import app.omnivore.omnivore.DatastoreRepository +import app.omnivore.omnivore.R import app.omnivore.omnivore.graphql.generated.SaveUrlMutation import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput +import app.omnivore.omnivore.ui.ResourceProvider import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional import dagger.hilt.android.lifecycle.HiltViewModel @@ -32,7 +34,8 @@ enum class SaveState { @HiltViewModel class SaveViewModel @Inject constructor( - private val datastoreRepo: DatastoreRepository + private val datastoreRepo: DatastoreRepository, + private val resourceProvider: ResourceProvider ) : ViewModel() { val saveState = MutableLiveData(SaveState.NONE) @@ -62,13 +65,13 @@ class SaveViewModel @Inject constructor( fun saveURL(url: String) { viewModelScope.launch { isLoading = true - message = "Saving to Omnivore..." + message = resourceProvider.getString(R.string.save_view_model_msg) saveState.postValue(SaveState.SAVING) val authToken = getAuthToken() if (authToken == null) { - message = "You are not logged in. Please login before saving." + message = resourceProvider.getString(R.string.save_view_model_error_not_logged_in) isLoading = false return@launch } @@ -103,15 +106,15 @@ class SaveViewModel @Inject constructor( val success = (response.data?.saveUrl?.onSaveSuccess?.url != null) message = if (success) { - "Page Saved" + resourceProvider.getString(R.string.save_view_model_page_saved_success) } else { - "There was an error saving your page" + resourceProvider.getString(R.string.save_view_model_page_saved_error) } saveState.postValue(SaveState.SAVED) Log.d(ContentValues.TAG, "Saved URL?: $success") } catch (e: java.lang.Exception) { - message = "There was an error saving your page" + message = resourceProvider.getString(R.string.save_view_model_page_saved_error) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt index 70769a7f7..25913bb8d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.library.SavedItemAction import app.omnivore.omnivore.ui.reader.WebReaderViewModel @@ -31,7 +32,7 @@ fun SavedItemContextMenu( onDismissRequest = onDismiss ) { DropdownMenuItem( - text = { Text("Edit Labels") }, + text = { Text(stringResource(R.string.saved_item_context_menu_action_edit_labels)) }, onClick = { actionHandler(SavedItemAction.EditLabels) onDismiss() @@ -44,7 +45,9 @@ fun SavedItemContextMenu( } ) DropdownMenuItem( - text = { Text(if (isArchived) "Unarchive" else "Archive") }, + text = { Text(if (isArchived) + stringResource(R.string.saved_item_context_menu_action_unarchive) else + stringResource(R.string.saved_item_context_menu_action_archive)) }, onClick = { val action = if (isArchived) SavedItemAction.Unarchive else SavedItemAction.Archive actionHandler(action) @@ -58,7 +61,7 @@ fun SavedItemContextMenu( } ) DropdownMenuItem( - text = { Text("Share Original") }, + text = { Text(stringResource(R.string.saved_item_context_menu_action_share_original)) }, onClick = { webReaderViewModel.showShareLinkSheet(context) onDismiss() @@ -71,7 +74,7 @@ fun SavedItemContextMenu( } ) DropdownMenuItem( - text = { Text("Remove Item") }, + text = { Text(stringResource(R.string.saved_item_context_menu_action_remove_item)) }, onClick = { actionHandler(SavedItemAction.Delete) onDismiss() diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/LogoutDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/LogoutDialog.kt index 7cb634467..569990988 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/LogoutDialog.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/LogoutDialog.kt @@ -5,6 +5,8 @@ import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import app.omnivore.omnivore.R import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInOptions @@ -14,9 +16,9 @@ fun LogoutDialog(onClose: (Boolean) -> Unit) { AlertDialog( onDismissRequest = { onClose(false) }, - title = { Text(text = "Logout") }, + title = { Text(text = stringResource(R.string.logout_dialog_title)) }, text = { - Text("Are you sure you want to logout?") + Text(stringResource(R.string.logout_dialog_confirm_msg)) }, confirmButton = { Button(onClick = { @@ -28,12 +30,12 @@ fun LogoutDialog(onClose: (Boolean) -> Unit) { googleSignIn.signOut() onClose(true) }) { - Text("Confirm") + Text(stringResource(R.string.logout_dialog_action_confirm)) } }, dismissButton = { Button(onClick = { onClose(false) }) { - Text("Cancel") + Text(stringResource(R.string.logout_dialog_action_cancel)) } } ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/ManageAccount.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/ManageAccount.kt index 2d85e440e..9d73fe181 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/ManageAccount.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/ManageAccount.kt @@ -14,8 +14,10 @@ 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 androidx.compose.ui.window.Dialog +import app.omnivore.omnivore.R @Composable fun ManageAccountDialog(onDismiss: () -> Unit, settingsViewModel: SettingsViewModel) { @@ -43,7 +45,7 @@ fun ManageAccountView(settingsViewModel: SettingsViewModel) { .padding(top = 12.dp, bottom = 12.dp), horizontalArrangement = Arrangement.Center ) { - Text("Manage Account") + Text(stringResource(R.string.manage_account_title)) } Column( @@ -55,7 +57,7 @@ fun ManageAccountView(settingsViewModel: SettingsViewModel) { modifier = Modifier .clickable(onClick = { settingsViewModel.resetDataCache() }) ) { - Text("Reset Data Cache") + Text(stringResource(R.string.manage_account_action_reset_data_cache)) Spacer(modifier = Modifier.weight(1.0F)) Icon(imageVector = Icons.Filled.Refresh, contentDescription = null) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/PolicyWebView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/PolicyWebView.kt index fa93fad06..984ce8430 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/PolicyWebView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/PolicyWebView.kt @@ -11,9 +11,11 @@ import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import androidx.navigation.NavHostController import app.omnivore.omnivore.Routes +import app.omnivore.omnivore.R @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SetJavaScriptEnabled") @OptIn(ExperimentalMaterial3Api::class) @@ -22,7 +24,7 @@ fun PolicyWebView(navController: NavHostController, url: String) { Scaffold( topBar = { TopAppBar( - title = { androidx.compose.material3.Text("Settings") }, + title = { androidx.compose.material3.Text(stringResource(R.string.policy_webview_title)) }, actions = { IconButton(onClick = { navController.navigate(Routes.Settings.route) }) { Icon( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt index 21a0baaf8..720745d75 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import app.omnivore.omnivore.R @@ -37,7 +38,7 @@ fun SettingsView( Scaffold( topBar = { TopAppBar( - title = { Text("Settings") }, + title = { Text(stringResource(R.string.settings_view_title)) }, actions = { IconButton(onClick = { navController.navigate(Routes.Library.route) }) { Icon( @@ -100,19 +101,31 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti // // SectionSpacer() - SettingRow(text = "Documentation") { navController.navigate(Routes.Documentation.route) } + SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) { + navController.navigate(Routes.Documentation.route) + } RowDivider() - SettingRow(text = "Feedback") { Intercom.client().present(space = IntercomSpace.Messages) } + SettingRow(text = stringResource(R.string.settings_view_setting_row_feedback)) { + Intercom.client().present(space = IntercomSpace.Messages) + } RowDivider() - SettingRow(text = "Privacy Policy") { navController.navigate(Routes.PrivacyPolicy.route) } + SettingRow(text = stringResource(R.string.settings_view_setting_row_privacy_policy)) { + navController.navigate(Routes.PrivacyPolicy.route) + } RowDivider() - SettingRow(text = "Terms and Conditions") { navController.navigate(Routes.TermsAndConditions.route) } + SettingRow(text = stringResource(R.string.settings_view_setting_row_terms_and_conditions)) { + navController.navigate(Routes.TermsAndConditions.route) + } SectionSpacer() - SettingRow(text = "Manage Account") { showManageAccountDialog.value = true } + SettingRow(text = stringResource(R.string.settings_view_setting_row_manage_account)) { + showManageAccountDialog.value = true + } RowDivider() - SettingRow(text = "Logout", includeIcon = false) { showLogoutDialog.value = true } + SettingRow(text = stringResource(R.string.settings_view_setting_row_logout), includeIcon = false) { + showLogoutDialog.value = true + } RowDivider() } diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml index f47c7b85c..ad7f2a3ea 100644 --- a/android/Omnivore/app/src/main/res/values/strings.xml +++ b/android/Omnivore/app/src/main/res/values/strings.xml @@ -12,4 +12,195 @@ Note Copy Note + + + Continue with Apple + Signing in... + + + Create Your Profile + Loading... + Cancel Sign Up + Submit + Name + Name + Username + Username + Please enter a valid name and username. + + + Loading... + Return to Social Login + Don\'t have an account? + Forgot your password? + Login + user@email.com + Email + Password + Password + Please enter an email address and password. + + + We\'ve sent a verification email to %1$s. Please verify your email and then tap the button below. + Check Status + Use a different email? + Loading... + Return to Social Login + Already have an account? + Sign Up + user@email.com + Email + Password + Password + Name + Name + Name + Name + Please complete all fields. + + + Continue with Google + Signing in... + + + Self-hosting settings updated. + Self-hosting settings reset. + Username must be between 4 and 15 characters long. + Username can contain only letters and numbers. + This username is not available. + Sorry we\'re having trouble connecting to the server. + Something went wrong. Please check your email/password and try again. + Something went wrong. Please check your credentials and try again. + Failed to authenticate with Google. + No authentication token found. + + + Loading... + Reset + Back + Save + Learn more about self-hosting Omnivore + API Server + Web Server + Please enter API Server and Web server addresses. + + + Dismiss + Continue with Email + Self-hosting options + + + Create New Label + Assign a name and color. + Create + Cancel + Label Name + + + Filter by Label + Set Labels + Cancel + Search + Save + Create a new label named \"%1$s\" + + + Labels + + + Library + + Search + + + Labels updated + Unable to set labels + + + Notebook + Copy + Notebook copied + + + Note + Save + Cancel + + + Article Notes + Add Notes... + + + Highlights + Copy + Highlight copied + Add Note... + You have not added any highlights to this page. + + + Font Size: + Margin + Line Spacing + Theme: + Auto + High Contrast Text + Justify Text + + + We were unable to fetch your content. + Reader Preferences + Notebook + Open Link + + + Open in Browser + Save to Omnivore + Copy Link + Cancel + + + Link saved + Error saving link + Link copied + + + Saving + Read Now + Read Later + Dismiss + + + Saving to Omnivore... + You are not logged in. Please login before saving. + Page Saved + There was an error saving your page + + + Edit Labels + Archive + Unarchive + Share Original + Remove Item + + + Logout + Are you sure you want to logout? + Confirm + Cancel + + + Manage Account + Reset Data Cache + + + Settings + + + Settings + Documentation + Feedback + Privacy Policy + Terms and Conditions + Manage Account + Logout