Localize auth screens (login, verify email, reset password) EN/RU
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-03-11 06:07:55 +03:00
parent e0728ac067
commit e591a3fa8d
5 changed files with 109 additions and 34 deletions

View File

@@ -20,13 +20,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ru.daemonlord.messenger.R
@Composable @Composable
fun LoginScreen( fun LoginScreen(
state: AuthUiState, state: AuthUiState,
headerTitle: String = "Messenger Login", headerTitle: String = "",
onEmailChanged: (String) -> Unit, onEmailChanged: (String) -> Unit,
onNameChanged: (String) -> Unit, onNameChanged: (String) -> Unit,
onUsernameChanged: (String) -> Unit, onUsernameChanged: (String) -> Unit,
@@ -42,6 +44,7 @@ fun LoginScreen(
) { ) {
val isTabletLayout = LocalConfiguration.current.screenWidthDp >= 840 val isTabletLayout = LocalConfiguration.current.screenWidthDp >= 840
val isBusy = state.isLoading val isBusy = state.isLoading
val resolvedHeaderTitle = headerTitle.ifBlank { stringResource(id = R.string.auth_header_login) }
Box( Box(
modifier = Modifier modifier = Modifier
@@ -58,16 +61,16 @@ fun LoginScreen(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text( Text(
text = headerTitle, text = resolvedHeaderTitle,
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 14.dp), modifier = Modifier.padding(bottom = 14.dp),
) )
val subtitle = when (state.step) { val subtitle = when (state.step) {
AuthStep.EMAIL -> "Enter your email to continue" AuthStep.EMAIL -> stringResource(id = R.string.auth_subtitle_enter_email)
AuthStep.PASSWORD -> "Enter password for ${state.email}" AuthStep.PASSWORD -> stringResource(id = R.string.auth_subtitle_enter_password, state.email)
AuthStep.REGISTER -> "Create account for ${state.email}" AuthStep.REGISTER -> stringResource(id = R.string.auth_subtitle_create_account, state.email)
AuthStep.OTP -> "Two-factor authentication is enabled" AuthStep.OTP -> stringResource(id = R.string.auth_subtitle_2fa_enabled)
} }
Text( Text(
text = subtitle, text = subtitle,
@@ -81,7 +84,7 @@ fun LoginScreen(
OutlinedTextField( OutlinedTextField(
value = state.email, value = state.email,
onValueChange = onEmailChanged, onValueChange = onEmailChanged,
label = { Text(text = "Email") }, label = { Text(text = stringResource(id = R.string.auth_label_email)) },
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier modifier = Modifier
@@ -96,7 +99,7 @@ fun LoginScreen(
if (isBusy) { if (isBusy) {
CircularProgressIndicator(strokeWidth = 2.dp, modifier = Modifier.padding(2.dp)) CircularProgressIndicator(strokeWidth = 2.dp, modifier = Modifier.padding(2.dp))
} else { } else {
Text("Continue") Text(stringResource(id = R.string.auth_continue))
} }
} }
} }
@@ -106,7 +109,7 @@ fun LoginScreen(
value = state.email, value = state.email,
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
label = { Text(text = "Email") }, label = { Text(text = stringResource(id = R.string.auth_label_email)) },
singleLine = true, singleLine = true,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -119,14 +122,14 @@ fun LoginScreen(
.align(Alignment.Start) .align(Alignment.Start)
.padding(bottom = 6.dp), .padding(bottom = 6.dp),
) { ) {
Text("Change email") Text(stringResource(id = R.string.auth_change_email))
} }
if (state.step == AuthStep.REGISTER) { if (state.step == AuthStep.REGISTER) {
OutlinedTextField( OutlinedTextField(
value = state.name, value = state.name,
onValueChange = onNameChanged, onValueChange = onNameChanged,
label = { Text(text = "Name") }, label = { Text(text = stringResource(id = R.string.auth_label_name)) },
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier modifier = Modifier
@@ -136,7 +139,7 @@ fun LoginScreen(
OutlinedTextField( OutlinedTextField(
value = state.username, value = state.username,
onValueChange = onUsernameChanged, onValueChange = onUsernameChanged,
label = { Text(text = "Username") }, label = { Text(text = stringResource(id = R.string.auth_label_username)) },
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier modifier = Modifier
@@ -149,7 +152,7 @@ fun LoginScreen(
OutlinedTextField( OutlinedTextField(
value = state.password, value = state.password,
onValueChange = onPasswordChanged, onValueChange = onPasswordChanged,
label = { Text(text = "Password") }, label = { Text(text = stringResource(id = R.string.auth_label_password)) },
visualTransformation = PasswordVisualTransformation(), visualTransformation = PasswordVisualTransformation(),
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
@@ -167,13 +170,19 @@ fun LoginScreen(
.align(Alignment.Start) .align(Alignment.Start)
.padding(bottom = 4.dp), .padding(bottom = 4.dp),
) { ) {
Text(if (state.useRecoveryCode) "Use OTP code" else "Use recovery code") Text(
if (state.useRecoveryCode) {
stringResource(id = R.string.auth_use_otp_code)
} else {
stringResource(id = R.string.auth_use_recovery_code)
},
)
} }
if (state.useRecoveryCode) { if (state.useRecoveryCode) {
OutlinedTextField( OutlinedTextField(
value = state.recoveryCode, value = state.recoveryCode,
onValueChange = onRecoveryCodeChanged, onValueChange = onRecoveryCodeChanged,
label = { Text(text = "Recovery code") }, label = { Text(text = stringResource(id = R.string.auth_label_recovery_code)) },
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier modifier = Modifier
@@ -184,7 +193,7 @@ fun LoginScreen(
OutlinedTextField( OutlinedTextField(
value = state.otpCode, value = state.otpCode,
onValueChange = onOtpCodeChanged, onValueChange = onOtpCodeChanged,
label = { Text(text = "2FA code") }, label = { Text(text = stringResource(id = R.string.auth_label_2fa_code)) },
singleLine = true, singleLine = true,
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier modifier = Modifier
@@ -204,10 +213,10 @@ fun LoginScreen(
} else { } else {
Text( Text(
when (state.step) { when (state.step) {
AuthStep.PASSWORD -> "Sign in" AuthStep.PASSWORD -> stringResource(id = R.string.auth_sign_in)
AuthStep.REGISTER -> "Create account" AuthStep.REGISTER -> stringResource(id = R.string.auth_create_account)
AuthStep.OTP -> "Confirm 2FA" AuthStep.OTP -> stringResource(id = R.string.auth_confirm_2fa)
AuthStep.EMAIL -> "Continue" AuthStep.EMAIL -> stringResource(id = R.string.auth_continue)
} }
) )
} }
@@ -237,13 +246,13 @@ fun LoginScreen(
enabled = !isBusy, enabled = !isBusy,
modifier = Modifier.padding(top = 8.dp), modifier = Modifier.padding(top = 8.dp),
) { ) {
Text(text = "Verify email by token") Text(text = stringResource(id = R.string.auth_verify_email_by_token))
} }
TextButton( TextButton(
onClick = onOpenResetPassword, onClick = onOpenResetPassword,
enabled = !isBusy, enabled = !isBusy,
) { ) {
Text(text = "Forgot password") Text(text = stringResource(id = R.string.auth_forgot_password))
} }
} }
} }

View File

@@ -23,9 +23,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.daemonlord.messenger.R
import ru.daemonlord.messenger.ui.account.AccountViewModel import ru.daemonlord.messenger.ui.account.AccountViewModel
@Composable @Composable
@@ -51,11 +53,11 @@ fun ResetPasswordRoute(
.then(if (isTabletLayout) Modifier.widthIn(max = 620.dp) else Modifier), .then(if (isTabletLayout) Modifier.widthIn(max = 620.dp) else Modifier),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
Text("Password reset", style = MaterialTheme.typography.headlineSmall) Text(stringResource(id = R.string.auth_password_reset_title), style = MaterialTheme.typography.headlineSmall)
OutlinedTextField( OutlinedTextField(
value = email, value = email,
onValueChange = { email = it }, onValueChange = { email = it },
label = { Text("Email") }, label = { Text(stringResource(id = R.string.auth_label_email)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
Button( Button(
@@ -63,12 +65,12 @@ fun ResetPasswordRoute(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = !state.isSaving && email.isNotBlank(), enabled = !state.isSaving && email.isNotBlank(),
) { ) {
Text("Send reset link") Text(stringResource(id = R.string.auth_send_reset_link))
} }
OutlinedTextField( OutlinedTextField(
value = password, value = password,
onValueChange = { password = it }, onValueChange = { password = it },
label = { Text("New password") }, label = { Text(stringResource(id = R.string.auth_new_password)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
Button( Button(
@@ -80,7 +82,7 @@ fun ResetPasswordRoute(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = !state.isSaving && !token.isNullOrBlank() && password.length >= 8, enabled = !state.isSaving && !token.isNullOrBlank() && password.length >= 8,
) { ) {
Text("Reset with token") Text(stringResource(id = R.string.auth_reset_with_token))
} }
if (state.isSaving) { if (state.isSaving) {
CircularProgressIndicator() CircularProgressIndicator()
@@ -92,7 +94,7 @@ fun ResetPasswordRoute(
Text(state.errorMessage!!, color = MaterialTheme.colorScheme.error) Text(state.errorMessage!!, color = MaterialTheme.colorScheme.error)
} }
Button(onClick = onBackToLogin, modifier = Modifier.fillMaxWidth()) { Button(onClick = onBackToLogin, modifier = Modifier.fillMaxWidth()) {
Text("Back to login") Text(stringResource(id = R.string.auth_back_to_login))
} }
} }
} }

View File

@@ -24,9 +24,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.daemonlord.messenger.R
import ru.daemonlord.messenger.ui.account.AccountViewModel import ru.daemonlord.messenger.ui.account.AccountViewModel
@Composable @Composable
@@ -59,11 +61,11 @@ fun VerifyEmailRoute(
.then(if (isTabletLayout) Modifier.widthIn(max = 620.dp) else Modifier), .then(if (isTabletLayout) Modifier.widthIn(max = 620.dp) else Modifier),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
Text("Verify email", style = MaterialTheme.typography.headlineSmall) Text(stringResource(id = R.string.auth_verify_email_title), style = MaterialTheme.typography.headlineSmall)
OutlinedTextField( OutlinedTextField(
value = editableToken, value = editableToken,
onValueChange = { editableToken = it }, onValueChange = { editableToken = it },
label = { Text("Verification token") }, label = { Text(stringResource(id = R.string.auth_verification_token)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
Button( Button(
@@ -71,12 +73,12 @@ fun VerifyEmailRoute(
enabled = !state.isSaving && editableToken.isNotBlank(), enabled = !state.isSaving && editableToken.isNotBlank(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text("Verify") Text(stringResource(id = R.string.auth_verify))
} }
OutlinedTextField( OutlinedTextField(
value = resendEmail, value = resendEmail,
onValueChange = { resendEmail = it }, onValueChange = { resendEmail = it },
label = { Text("Email for resend") }, label = { Text(stringResource(id = R.string.auth_email_for_resend)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
@@ -85,7 +87,7 @@ fun VerifyEmailRoute(
enabled = !state.isSaving && resendEmail.isNotBlank(), enabled = !state.isSaving && resendEmail.isNotBlank(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text("Resend verification link") Text(stringResource(id = R.string.auth_resend_verification_link))
} }
if (state.isSaving) { if (state.isSaving) {
CircularProgressIndicator() CircularProgressIndicator()
@@ -97,7 +99,7 @@ fun VerifyEmailRoute(
Text(state.errorMessage!!, color = MaterialTheme.colorScheme.error) Text(state.errorMessage!!, color = MaterialTheme.colorScheme.error)
} }
Button(onClick = onBackToLogin, modifier = Modifier.fillMaxWidth()) { Button(onClick = onBackToLogin, modifier = Modifier.fillMaxWidth()) {
Text("Back to login") Text(stringResource(id = R.string.auth_back_to_login))
} }
} }
} }

View File

@@ -177,4 +177,35 @@
<string name="privacy_everyone">все</string> <string name="privacy_everyone">все</string>
<string name="privacy_contacts">контакты</string> <string name="privacy_contacts">контакты</string>
<string name="privacy_nobody">никто</string> <string name="privacy_nobody">никто</string>
<string name="auth_header_login">Вход в Messenger</string>
<string name="auth_subtitle_enter_email">Введите email для продолжения</string>
<string name="auth_subtitle_enter_password">Введите пароль для %1$s</string>
<string name="auth_subtitle_create_account">Создайте аккаунт для %1$s</string>
<string name="auth_subtitle_2fa_enabled">Включена двухфакторная аутентификация</string>
<string name="auth_label_email">Email</string>
<string name="auth_label_name">Имя</string>
<string name="auth_label_username">Имя пользователя</string>
<string name="auth_label_password">Пароль</string>
<string name="auth_label_recovery_code">Код восстановления</string>
<string name="auth_label_2fa_code">Код 2FA</string>
<string name="auth_continue">Продолжить</string>
<string name="auth_change_email">Изменить email</string>
<string name="auth_use_otp_code">Использовать OTP-код</string>
<string name="auth_use_recovery_code">Использовать код восстановления</string>
<string name="auth_sign_in">Войти</string>
<string name="auth_create_account">Создать аккаунт</string>
<string name="auth_confirm_2fa">Подтвердить 2FA</string>
<string name="auth_verify_email_by_token">Подтвердить email по токену</string>
<string name="auth_forgot_password">Забыли пароль</string>
<string name="auth_verify_email_title">Подтверждение email</string>
<string name="auth_verification_token">Токен подтверждения</string>
<string name="auth_verify">Подтвердить</string>
<string name="auth_email_for_resend">Email для повторной отправки</string>
<string name="auth_resend_verification_link">Отправить ссылку повторно</string>
<string name="auth_back_to_login">Назад ко входу</string>
<string name="auth_password_reset_title">Сброс пароля</string>
<string name="auth_send_reset_link">Отправить ссылку сброса</string>
<string name="auth_new_password">Новый пароль</string>
<string name="auth_reset_with_token">Сбросить по токену</string>
</resources> </resources>

View File

@@ -177,4 +177,35 @@
<string name="privacy_everyone">everyone</string> <string name="privacy_everyone">everyone</string>
<string name="privacy_contacts">contacts</string> <string name="privacy_contacts">contacts</string>
<string name="privacy_nobody">nobody</string> <string name="privacy_nobody">nobody</string>
<string name="auth_header_login">Messenger Login</string>
<string name="auth_subtitle_enter_email">Enter your email to continue</string>
<string name="auth_subtitle_enter_password">Enter password for %1$s</string>
<string name="auth_subtitle_create_account">Create account for %1$s</string>
<string name="auth_subtitle_2fa_enabled">Two-factor authentication is enabled</string>
<string name="auth_label_email">Email</string>
<string name="auth_label_name">Name</string>
<string name="auth_label_username">Username</string>
<string name="auth_label_password">Password</string>
<string name="auth_label_recovery_code">Recovery code</string>
<string name="auth_label_2fa_code">2FA code</string>
<string name="auth_continue">Continue</string>
<string name="auth_change_email">Change email</string>
<string name="auth_use_otp_code">Use OTP code</string>
<string name="auth_use_recovery_code">Use recovery code</string>
<string name="auth_sign_in">Sign in</string>
<string name="auth_create_account">Create account</string>
<string name="auth_confirm_2fa">Confirm 2FA</string>
<string name="auth_verify_email_by_token">Verify email by token</string>
<string name="auth_forgot_password">Forgot password</string>
<string name="auth_verify_email_title">Verify email</string>
<string name="auth_verification_token">Verification token</string>
<string name="auth_verify">Verify</string>
<string name="auth_email_for_resend">Email for resend</string>
<string name="auth_resend_verification_link">Resend verification link</string>
<string name="auth_back_to_login">Back to login</string>
<string name="auth_password_reset_title">Password reset</string>
<string name="auth_send_reset_link">Send reset link</string>
<string name="auth_new_password">New password</string>
<string name="auth_reset_with_token">Reset with token</string>
</resources> </resources>