Skip to content

Commit

Permalink
login: translations
Browse files Browse the repository at this point in the history
  • Loading branch information
sirpengi committed Sep 25, 2023
1 parent 2a8b6a3 commit 8b94b29
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 21 deletions.
92 changes: 92 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,82 @@
"@lightboxCopyLinkSuccessToast": {
"description": "Success message in lightbox after copy link action completed."
},
"loginPageTitle": "Log in",
"@loginPageTitle": {
"description": "Page title for login page."
},
"loginFormSubmitLabel": "Log in",
"@loginFormSubmitLabel": {
"description": "Button text to submit login credentials."
},
"loginAddAnAccount": "Add an account",
"@loginAddAnAccount": {
"description": "Page title for screen to add a zulip account."
},
"loginServerLoginFailed": "Login failed",
"@loginServerLoginFailed": {
"description": "Error dialog title when login for a Zulip server fails."
},
"loginServerUrlInputLabel": "Your Zulip server URL",
"@loginServerUrlInputLabel": {
"description": "Input label in login page for Zulip server URL entry."
},
"loginServerErrorMessage": "The server said:\n\n{message}",
"@loginServerErrorMessage": {
"description": "Error message for login that quotes an error from the server.",
"placeholders": {
"message": {
"type": "String",
"example": "Invalid format"
}
}
},
"loginCouldNotConnectMessage": "Failed to connect to server:\n{url}",
"@loginCouldNotConnectMessage": {
"description": "Message for login dialog the app could not connect to the server.",
"placeholders": {
"url": {
"type": "String",
"example": "http://example.com/"
}
}
},
"loginCouldNotConnectTitle": "Could not connect",
"@loginCouldNotConnectTitle": {
"description": "Title for login dialog the app could not connect to the server."
},
"loginHidePassword": "Hide password",
"@loginHidePassword": {
"description": "Icon label for button to hide password in input form."
},
"loginInvalidInput": "Invalid input",
"@loginInvalidInput": {
"description": "Title for login dialog when input is invalid."
},
"loginValidationPassword": "Please enter your password",
"@loginValidationPassword": {
"description": "Prompt for input for password field."
},
"loginValidationPasswordLabel": "Password",
"@loginValidationPasswordLabel": {
"description": "Label for input for password field."
},
"loginValidationRequireEmail": "Please enter your email",
"@loginValidationRequireEmail": {
"description": "Prompt when an email is required to login."
},
"loginValidationRequireEmailLabel": "Email",
"@loginValidationRequireEmailLabel": {
"description": "Label for input when an email is required to login."
},
"loginValidationRequireUsername": "Please enter your username",
"@loginValidationRequireUsername": {
"description": "Prompt when a username is required to login."
},
"loginValidationRequireUsernameLabel": "Username",
"@loginValidationRequireUsernameLabel": {
"description": "Label for input when a username is required to login."
},
"subscribedToNStreams": "Subscribed to {num, plural, =0{no streams} =1{1 stream} other{{num} streams}}",
"@subscribedToNStreams": {
"description": "Test page label showing number of streams user is subscribed to.",
Expand Down Expand Up @@ -113,6 +189,22 @@
}
}
},
"serverUrlValidationErrorEmpty": "Please enter a URL.",
"@serverUrlValidationErrorEmpty": {
"description": "Server validation error message when URL is empty"
},
"serverUrlValidationErrorInvalidUrl": "Please enter a valid URL.",
"@serverUrlValidationErrorInvalidUrl": {
"description": "Server validation error message when URL is not in a valid format."
},
"serverUrlValidationErrorNoUseEmail": "Please enter the server URL, not your email.",
"@serverUrlValidationErrorNoUseEmail": {
"description": "Server validation error message when URL provided looks like an email"
},
"serverUrlValidationErrorUnsupportedScheme": "The server URL must start with http:// or https://.",
"@serverUrlValidationErrorUnsupportedScheme": {
"description": "Server validation error message when URL does not have a scheme defined."
},
"userRoleOwner": "Owner",
"@userRoleOwner": {
"description": "Label for UserRole.owner"
Expand Down
53 changes: 32 additions & 21 deletions lib/widgets/login.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';

import '../api/core.dart';
import '../api/exception.dart';
Expand Down Expand Up @@ -42,17 +43,17 @@ enum ServerUrlValidationError {
}
}

String message() { // TODO(i18n)
String translatedMessage(ZulipLocalizations zulipLocalizations) {
switch (this) {
case empty:
return 'Please enter a URL.';
return zulipLocalizations.serverUrlValidationErrorEmpty;
case invalidUrl:
return 'Please enter a valid URL.';
return zulipLocalizations.serverUrlValidationErrorInvalidUrl;
case noUseEmail:
return 'Please enter the server URL, not your email.';
return zulipLocalizations.serverUrlValidationErrorNoUseEmail;
case unsupportedSchemeZulip:
case unsupportedSchemeOther:
return 'The server URL must start with http:// or https://.';
return zulipLocalizations.serverUrlValidationErrorUnsupportedScheme;
}
}
}
Expand Down Expand Up @@ -135,11 +136,13 @@ class _AddAccountPageState extends State<AddAccountPage> {
}

Future<void> _onSubmitted(BuildContext context) async {
final zulipLocalizations = ZulipLocalizations.of(context);
final url = _parseResult.url;
final error = _parseResult.error;
if (error != null) {
showErrorDialog(context: context,
title: 'Invalid input', message: error.message());
title: zulipLocalizations.loginInvalidInput,
message: error.translatedMessage(zulipLocalizations));
return;
}
assert(url != null);
Expand All @@ -158,7 +161,8 @@ class _AddAccountPageState extends State<AddAccountPage> {
// TODO(#105) give more helpful feedback; see `fetchServerSettings`
// in zulip-mobile's src/message/fetchActions.js.
showErrorDialog(context: context,
title: 'Could not connect', message: 'Failed to connect to server:\n$url');
title: zulipLocalizations.loginCouldNotConnectTitle,
message: zulipLocalizations.loginCouldNotConnectMessage(url.toString()));
return;
}
// https://github.com/dart-lang/linter/issues/4007
Expand All @@ -180,13 +184,14 @@ class _AddAccountPageState extends State<AddAccountPage> {
@override
Widget build(BuildContext context) {
assert(!PerAccountStoreWidget.debugExistsOf(context));
final zulipLocalizations = ZulipLocalizations.of(context);
final error = _parseResult.error;
final errorText = error == null || error.shouldDeferFeedback()
? null
: error.message();
: error.translatedMessage(zulipLocalizations);

return Scaffold(
appBar: AppBar(title: const Text('Add an account'),
appBar: AppBar(title: Text(zulipLocalizations.loginAddAnAccount),
bottom: _inProgress
? const PreferredSize(preferredSize: Size.fromHeight(4),
child: LinearProgressIndicator(minHeight: 4)) // 4 restates default
Expand All @@ -211,7 +216,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
// …but leave out unfocusing the input in case more editing is needed.
},
decoration: InputDecoration(
labelText: 'Your Zulip server URL',
labelText: zulipLocalizations.loginServerUrlInputLabel,
errorText: errorText,
helperText: kLayoutPinningHelperText,
hintText: 'your-org.zulipchat.com')),
Expand All @@ -220,7 +225,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
onPressed: !_inProgress && errorText == null
? () => _onSubmitted(context)
: null,
child: const Text('Continue')),
child: Text(zulipLocalizations.dialogButtonContinue)),
])))));
}
}
Expand Down Expand Up @@ -289,10 +294,13 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
// TODO(#105) give more helpful feedback. The RN app is
// unhelpful here; we should at least recognize invalid auth errors, and
// errors for deactivated user or realm (see zulip-mobile#4571).
final zulipLocalizations = ZulipLocalizations.of(context);
final message = (e is ZulipApiException)
? 'The server said:\n\n${e.message}'
? zulipLocalizations.loginServerErrorMessage(e.toTranslatedString(zulipLocalizations))
: e.message;
showErrorDialog(context: context, title: 'Login failed', message: message);
showErrorDialog(context: context,
title: zulipLocalizations.loginServerLoginFailed,
message: message);
return;
}

Expand Down Expand Up @@ -335,6 +343,7 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
@override
Widget build(BuildContext context) {
assert(!PerAccountStoreWidget.debugExistsOf(context));
final zulipLocalizations = ZulipLocalizations.of(context);
final requireEmailFormatUsernames = widget.serverSettings.requireEmailFormatUsernames;

final usernameField = TextFormField(
Expand All @@ -350,8 +359,8 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
validator: (value) {
if (value == null || value.trim().isEmpty) {
return requireEmailFormatUsernames
? 'Please enter your email.'
: 'Please enter your username.';
? zulipLocalizations.loginValidationRequireEmail
: zulipLocalizations.loginValidationRequireUsername;
}
if (requireEmailFormatUsernames) {
// TODO(#106): validate is in the shape of an email
Expand All @@ -360,7 +369,9 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: requireEmailFormatUsernames ? 'Email address' : 'Username',
labelText: requireEmailFormatUsernames
? zulipLocalizations.loginValidationRequireEmailLabel
: zulipLocalizations.loginValidationRequireUsernameLabel,
helperText: kLayoutPinningHelperText,
));

Expand All @@ -372,14 +383,14 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password.';
return zulipLocalizations.loginValidationPassword;
}
return null;
},
textInputAction: TextInputAction.go,
onFieldSubmitted: (value) => _submit(),
decoration: InputDecoration(
labelText: 'Password',
labelText: zulipLocalizations.loginValidationPasswordLabel,
helperText: kLayoutPinningHelperText,
// TODO(material-3): Simplify away `Semantics` by using IconButton's
// M3-only params `isSelected` / `selectedIcon`, after fixing
Expand All @@ -389,14 +400,14 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
// [ButtonStyleButton].)
suffixIcon: Semantics(toggled: _obscurePassword,
child: IconButton(
tooltip: 'Hide password',
tooltip: zulipLocalizations.loginHidePassword,
onPressed: _handlePasswordVisibilityPress,
icon: _obscurePassword
? const Icon(Icons.visibility_off)
: const Icon(Icons.visibility)))));

return Scaffold(
appBar: AppBar(title: const Text('Log in'),
appBar: AppBar(title: Text(zulipLocalizations.loginPageTitle),
bottom: _inProgress
? const PreferredSize(preferredSize: Size.fromHeight(4),
child: LinearProgressIndicator(minHeight: 4)) // 4 restates default
Expand All @@ -416,7 +427,7 @@ class _PasswordLoginPageState extends State<PasswordLoginPage> {
const SizedBox(height: 8),
ElevatedButton(
onPressed: _inProgress ? null : _submit,
child: const Text('Log in')),
child: Text(zulipLocalizations.loginFormSubmitLabel)),
])))))));
}
}

0 comments on commit 8b94b29

Please sign in to comment.