Skip to content

Localization sweep #306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 283 additions & 13 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,298 @@
"@profileButtonSendDirectMessage": {
"description": "Label for button in profile screen to navigate to DMs with the shown user."
},
"cameraAccessDeniedTitle": "Permissions needed",
"@cameraAccessDeniedTitle": {
"description": "Title for dialog when the user needs to grant permissions for camera access."
"permissionsNeededTitle": "Permissions needed",
"@permissionsNeededTitle": {
"description": "Title for dialog asking the user to grant additional permissions."
},
"cameraAccessDeniedMessage": "To upload an image, please grant Zulip additional permissions in Settings.",
"@cameraAccessDeniedMessage": {
"description": "Message for dialog when the user needs to grant permissions for camera access."
"permissionsNeededOpenSettings": "Open settings",
"@permissionsNeededOpenSettings": {
"description": "Button label for permissions dialog button that opens the system settings screen."
},
"cameraAccessDeniedButtonText": "Open settings",
"@cameraAccessDeniedButtonText": {
"description": "Message for dialog when the user needs to grant permissions for camera access."
"permissionsDeniedCameraAccess": "To upload an image, please grant Zulip additional permissions in Settings.",
"@permissionsDeniedCameraAccess": {
"description": "Message for dialog asking the user to grant permissions for camera access."
},
"permissionsDeniedReadExternalStorage": "To upload files, please grant Zulip additional permissions in Settings.",
"@permissionsDeniedReadExternalStorage": {
"description": "Message for dialog asking the user to grant permissions for external storage read access."
},
"actionSheetOptionCopy": "Copy message text",
"@actionSheetOptionCopy": {
"description": "Label for copy message text button on action sheet."
},
"actionSheetOptionShare": "Share",
"@actionSheetOptionShare": {
"description": "Label for share button on action sheet."
},
"actionSheetOptionQuoteAndReply": "Quote and reply",
"@actionSheetOptionQuoteAndReply": {
"description": "Label for Quote and reply button on action sheet."
},
"errorCouldNotFetchMessageSource": "Could not fetch message source",
"@errorCouldNotFetchMessageSource": {
"description": "Error message when the source of a message could not be fetched."
},
"errorCopyingFailed": "Copying failed",
"@errorCopyingFailed": {
"description": "Error message when copying the text of a message to the user's system clipboard failed."
},
"errorFailedToUploadFileTitle": "Failed to upload file: {filename}",
"@errorFailedToUploadFileTitle": {
"description": "Error title when the specified file failed to upload.",
"placeholders": {
"filename": {"type": "String", "example": "file.txt"}
}
},
"errorFilesTooLarge": "{num, plural, =1{File is} other{{num} files are}} larger than the server's limit of {maxFileUploadSizeMib} MiB and will not be uploaded:\n\n{listMessage}",
"@errorFilesTooLarge": {
"description": "Error message when attached files are too large in size.",
"placeholders": {
"num": {"type": "int", "example": "2"},
"maxFileUploadSizeMib": {"type": "int", "example": "15"},
"listMessage": {"type": "String", "example": "foo.txt\nbar.txt"}
}
},
"errorFilesTooLargeTitle": "{num, plural, =1{File} other{Files}} too large",
"@errorFilesTooLargeTitle": {
"description": "Error title when attached files are too large in size.",
"placeholders": {
"num": {"type": "int", "example": "4"}
}
},
"errorLoginInvalidInputTitle": "Invalid input",
"@errorLoginInvalidInputTitle": {
"description": "Error title for login when input is invalid."
},
"errorLoginFailedTitle": "Login failed",
"@errorLoginFailedTitle": {
"description": "Error title for login when signing into a Zulip server fails."
},
"errorMessageNotSent": "Message not sent",
"@errorMessageNotSent": {
"description": "Error message for compose box when a message could not be sent."
},
"errorLoginCouldNotConnect": "Failed to connect to server:\n{url}",
"@errorLoginCouldNotConnect": {
"description": "Error message when the app could not connect to the server.",
"placeholders": {
"url": {"type": "String", "example": "http://example.com/"}
}
},
"errorLoginCouldNotConnectTitle": "Could not connect",
"@errorLoginCouldNotConnectTitle": {
"description": "Error title when the app could not connect to the server."
},
"errorMessageDoesNotSeemToExist": "That message does not seem to exist.",
"@errorMessageDoesNotSeemToExist": {
"description": "Error message when loading a message that does not exist."
},
"errorQuotationFailed": "Quotation failed",
"@errorQuotationFailed": {
"description": "Error message when quoting a message failed."
},
"errorServerMessage": "The server said:\n\n{message}",
"@errorServerMessage": {
"description": "Error message that quotes an error from the server.",
"placeholders": {
"message": {"type": "String", "example": "Invalid format"}
}
},
"successLinkCopied": "Link copied",
"@successLinkCopied": {
"description": "Success message after copy link action completed."
},
"successMessageCopied": "Message Copied",
"@successMessageCopied": {
"description": "Message when content of a message was copied to the user's system clipboard."
},
"composeBoxAttachFilesTooltip": "Attach files",
"@composeBoxAttachFilesTooltip": {
"description": "Tooltip for compose box icon to attach a file to the message."
},
"composeBoxAttachMediaTooltip": "Attach images or videos",
"@composeBoxAttachMediaTooltip": {
"description": "Tooltip for compose box icon to attach media to the message."
},
"composeBoxAttachFromCameraTooltip": "Take a photo",
"@composeBoxAttachFromCameraTooltip": {
"description": "Tooltip for compose box icon to attach an image from the camera to the message."
},
"composeBoxGenericContentHint": "Type a message",
"@composeBoxGenericContentHint": {
"description": "Hint text for content input when sending a message."
},
"composeBoxDmContentHint": "Message @{user}",
"@composeBoxDmContentHint": {
"description": "Hint text for content input when sending a message to one other person.",
"placeholders": {
"user": {"type": "String", "example": "stream name"}
}
},
"composeBoxGroupDmContentHint": "Message group",
"@composeBoxGroupDmContentHint": {
"description": "Hint text for content input when sending a message to a group."
},
"composeBoxSelfDmContentHint": "Jot down something",
"@composeBoxSelfDmContentHint": {
"description": "Hint text for content input when sending a message to yourself."
},
"composeBoxStreamContentHint": "Message #{stream} > {topic}",
"@composeBoxStreamContentHint": {
"description": "Hint text for content input when sending a message to a stream",
"placeholders": {
"stream": {"type": "String", "example": "stream name"},
"topic": {"type": "String", "example": "topic name"}
}
},
"composeBoxSendTooltip": "Send",
"@composeBoxSendTooltip": {
"description": "Tooltip for send button in compose box."
},
"composeBoxUnknownStreamName": "(unknown stream)",
"@composeBoxUnknownStreamName": {
"description": "Replacement name for stream when it cannot be found in the store."
},
"composeBoxTopicHintText": "Topic",
"@composeBoxTopicHintText": {
"description": "Hint text for topic input widget in compose box."
},
"composeBoxUploadingFilename": "Uploading {filename}...",
"@composeBoxUploadingFilename": {
"description": "Placeholder in compose box showing the specified file is currently uploading.",
"placeholders": {
"filename": {"type": "String", "example": "file.txt"}
}
},
"contentValidationErrorTooLong": "Message length shouldn't be greater than 10000 characters.",
"@contentValidationErrorTooLong": {
"description": "Content validation error message when the message is too long."
},
"contentValidationErrorEmpty": "You have nothing to send!",
"@contentValidationErrorEmpty": {
"description": "Content validation error message when the message is empty."
},
"contentValidationErrorQuoteAndReplyInProgress": "Please wait for the quotation to complete.",
"@contentValidationErrorQuoteAndReplyInProgress": {
"description": "Content validation error message when a quotation has not completed yet."
},
"contentValidationErrorUploadInProgress": "Please wait for the upload to complete.",
"@contentValidationErrorUploadInProgress": {
"description": "Content validation error message when attachments have not finished uploading."
},
"dialogCancel": "Cancel",
"@dialogCancel": {
"description": "Button label in dialogs to cancel."
},
"dialogContinue": "Continue",
"@dialogContinue": {
"description": "Button label in dialogs to proceed."
},
"errorDialogContinue": "OK",
"@errorDialogContinue": {
"description": "Button label in error dialogs to acknowledge the error and close the dialog."
},
"errorDialogTitle": "Error",
"@errorDialogTitle": {
"description": "Generic title for error dialog."
},
"lightboxCopyLinkTooltip": "Copy link",
"@lightboxCopyLinkTooltip": {
"description": "Tooltip in lightbox for the copy link action."
},
"loginPageTitle": "Log in",
"@loginPageTitle": {
"description": "Page title for login page."
},
"loginFormSubmitLabel": "Log in",
"@loginFormSubmitLabel": {
"description": "Button text to submit login credentials."
},
"loginAddAnAccountPageTitle": "Add an account",
"@loginAddAnAccountPageTitle": {
"description": "Page title for screen to add a Zulip account."
},
"loginServerUrlInputLabel": "Your Zulip server URL",
"@loginServerUrlInputLabel": {
"description": "Input label in login page for Zulip server URL entry."
},
"loginHidePassword": "Hide password",
"@loginHidePassword": {
"description": "Icon label for button to hide password in input form."
},
"loginEmailLabel": "Email address",
"@loginEmailLabel": {
"description": "Label for input when an email is required to log in."
},
"loginErrorMissingEmail": "Please enter your email.",
"@loginErrorMissingEmail": {
"description": "Error message when an empty email was provided."
},
"loginPasswordLabel": "Password",
"@loginPasswordLabel": {
"description": "Label for password input field."
},
"loginErrorMissingPassword": "Please enter your password.",
"@loginErrorMissingPassword": {
"description": "Error message when an empty password was provided."
},
"loginUsernameLabel": "Username",
"@loginUsernameLabel": {
"description": "Label for input when a username is required to log in."
},
"loginErrorMissingUsername": "Please enter your username.",
"@loginErrorMissingUsername": {
"description": "Error message when an empty username was provided."
},
"topicValidationErrorTooLong": "Topic length shouldn't be greater than 60 characters.",
"@topicValidationErrorTooLong": {
"description": "Topic validation error when topic is too long."
},
"topicValidationErrorMandatoryButEmpty": "Topics are required in this organization.",
"@topicValidationErrorMandatoryButEmpty": {
"description": "Topic validation error when topic is required but was empty."
},
"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.",
"placeholders": {
"num": {
"type": "int",
"example": "4"
}
"num": {"type": "int", "example": "4"}
}
},
"errorNetworkRequestFailed": "Network request failed",
"@errorNetworkRequestFailed": {
"description": "Error message when a network request fails."
},
"errorMalformedResponse": "Server gave malformed response; HTTP status {httpStatus}",
"@errorMalformedResponse": {
"description": "Error message when an API call fails because we could not parse the response.",
"placeholders": {
"httpStatus": {"type": "int", "example": "200"}
}
},
"errorRequestFailed": "Network request failed: HTTP status {httpStatus}",
"@errorRequestFailed": {
"description": "Error message when an API call fails.",
"placeholders": {
"httpStatus": {"type": "int", "example": "500"}
}
},
"serverUrlValidationErrorEmpty": "Please enter a URL.",
"@serverUrlValidationErrorEmpty": {
"description": "Error message when URL is empty"
},
"serverUrlValidationErrorInvalidUrl": "Please enter a valid URL.",
"@serverUrlValidationErrorInvalidUrl": {
"description": "Error message when URL is not in a valid format."
},
"serverUrlValidationErrorNoUseEmail": "Please enter the server URL, not your email.",
"@serverUrlValidationErrorNoUseEmail": {
"description": "Error message when URL looks like an email"
},
"serverUrlValidationErrorUnsupportedScheme": "The server URL must start with http:// or https://.",
"@serverUrlValidationErrorUnsupportedScheme": {
"description": "Error message when URL has an unsupported scheme."
},
"userRoleOwner": "Owner",
"@userRoleOwner": {
"description": "Label for UserRole.owner"
Expand Down
4 changes: 3 additions & 1 deletion lib/api/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:io';
import 'package:http/http.dart' as http;

import '../log.dart';
import '../model/localizations.dart';
import 'exception.dart';

/// A value for an API request parameter, to use directly without JSON encoding.
Expand Down Expand Up @@ -90,7 +91,8 @@ class ApiConnection {
} else if (e is TlsException) {
message = e.message;
} else {
message = 'Network request failed';
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
message = zulipLocalizations.errorNetworkRequestFailed;
}
throw NetworkException(routeName: routeName, cause: e, message: message);
}
Expand Down
11 changes: 9 additions & 2 deletions lib/api/exception.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import '../model/localizations.dart';

/// Some kind of error from a Zulip API network request.
sealed class ApiRequestException implements Exception {
/// The name of the Zulip API route for the request.
Expand All @@ -18,6 +20,9 @@ sealed class ApiRequestException implements Exception {
final String message;

ApiRequestException({required this.routeName, required this.message});

@override
String toString() => message;
}

/// An error returned through the Zulip server API.
Expand Down Expand Up @@ -89,7 +94,8 @@ class Server5xxException extends ServerException {
required super.httpStatus,
required super.data,
}) : assert(500 <= httpStatus && httpStatus <= 599),
super(message: 'Network request failed: HTTP status $httpStatus'); // TODO(i18n)
super(message: GlobalLocalizations.zulipLocalizations
.errorRequestFailed(httpStatus));
}

/// An error where the server's response doesn't match the Zulip API.
Expand All @@ -110,5 +116,6 @@ class MalformedServerResponseException extends ServerException {
required super.routeName,
required super.httpStatus,
required super.data,
}) : super(message: 'Server gave malformed response; HTTP status $httpStatus'); // TODO(i18n)
}) : super(message: GlobalLocalizations.zulipLocalizations
.errorMalformedResponse(httpStatus));
}
6 changes: 6 additions & 0 deletions lib/model/localizations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';

abstract final class GlobalLocalizations {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

model: Add GlobalLocalization class for static access to localizations

commit message should match code on name of class

static ZulipLocalizations zulipLocalizations =
lookupZulipLocalizations(ZulipLocalizations.supportedLocales.first);
}
Loading