l10n: Extract strings [Part-1] (#955)

This commit is contained in:
Neeraj Gupta 2023-04-05 08:24:25 +05:30 committed by GitHub
commit f3e2c2d962
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 2112 additions and 256 deletions

View file

@ -20,18 +20,260 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary { class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'en'; String get localeName => 'en';
static String m0(user) =>
"${user} will not be able to add more photos to this album\n\nThey will still be able to remove existing photos added by them";
static String m1(supportEmail) =>
"Please drop an email to ${supportEmail} from your registered email address";
static String m2(passwordStrengthValue) =>
"Password strength: ${passwordStrengthValue}";
final messages = _notInlinedMessages(_notInlinedMessages); final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"accountWelcomeBack": "accountWelcomeBack":
MessageLookupByLibrary.simpleMessage("Welcome back!"), MessageLookupByLibrary.simpleMessage("Welcome back!"),
"ackPasswordLostWarningPart1": MessageLookupByLibrary.simpleMessage(
"I understand that if I lose my password, I may lose my data since my data is "),
"ackPasswordLostWarningPart2":
MessageLookupByLibrary.simpleMessage(" with ente"),
"activeSessions":
MessageLookupByLibrary.simpleMessage("Active sessions"),
"addANewEmail": MessageLookupByLibrary.simpleMessage("Add a new email"),
"addCollaborator":
MessageLookupByLibrary.simpleMessage("Add collaborator"),
"addMore": MessageLookupByLibrary.simpleMessage("Add more"),
"addViewer": MessageLookupByLibrary.simpleMessage("Add viewer"),
"addedAs": MessageLookupByLibrary.simpleMessage("Added as"),
"albumOwner": MessageLookupByLibrary.simpleMessage("Owner"),
"and": MessageLookupByLibrary.simpleMessage("and"),
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
"What is the main reason you are deleting your account?"),
"byClickingLogInIAgreeToThe": MessageLookupByLibrary.simpleMessage(
"By clicking log in, I agree to the"),
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"), "cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
"cannotAddMorePhotosAfterBecomingViewer": m0,
"changeEmail": MessageLookupByLibrary.simpleMessage("Change email"),
"changePasswordTitle":
MessageLookupByLibrary.simpleMessage("Change password"),
"changePermissions":
MessageLookupByLibrary.simpleMessage("Change permissions?"),
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
"Please check your inbox (and spam) to complete verification"),
"codeCopiedToClipboard":
MessageLookupByLibrary.simpleMessage("Code copied to clipboard"),
"collaborator": MessageLookupByLibrary.simpleMessage("Collaborator"),
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum":
MessageLookupByLibrary.simpleMessage(
"Collaborators can add photos and videos to the shared album."),
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
"confirmAccountDeletion":
MessageLookupByLibrary.simpleMessage("Confirm Account Deletion"),
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
"Yes, I want to permanently delete this account and all its data."),
"confirmPassword":
MessageLookupByLibrary.simpleMessage("Confirm password"),
"confirmRecoveryKey":
MessageLookupByLibrary.simpleMessage("Confirm recovery key"),
"confirmYourRecoveryKey":
MessageLookupByLibrary.simpleMessage("Confirm your recovery key"),
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contact support"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continue"),
"copypasteThisCodentoYourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(
"Copy-paste this code\nto your authenticator app"),
"createAccount": MessageLookupByLibrary.simpleMessage("Create account"),
"createNewAccount":
MessageLookupByLibrary.simpleMessage("Create new account"),
"decrypting": MessageLookupByLibrary.simpleMessage("Decrypting..."),
"deleteAccount": MessageLookupByLibrary.simpleMessage("Delete account"),
"deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage(
"We are sorry to see you go. Please share your feedback to help us improve."),
"deleteAccountPermanentlyButton":
MessageLookupByLibrary.simpleMessage("Delete Account Permanently"),
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
"You are about to permanently delete your account and all its data.\nThis action is irreversible."),
"deleteReason1": MessageLookupByLibrary.simpleMessage(
"Its missing a key feature that I need"),
"deleteReason2": MessageLookupByLibrary.simpleMessage(
"The app or a certain feature does not \nbehave as I think it should"),
"deleteReason3": MessageLookupByLibrary.simpleMessage(
"I found another service that I like better"),
"deleteReason4":
MessageLookupByLibrary.simpleMessage("My reason isnt listed"),
"deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
"Your request will be processed within 72 hours."),
"doThisLater": MessageLookupByLibrary.simpleMessage("Do this later"),
"dropSupportEmail": m1,
"email": MessageLookupByLibrary.simpleMessage("Email"), "email": MessageLookupByLibrary.simpleMessage("Email"),
"encryption": MessageLookupByLibrary.simpleMessage("Encryption"),
"encryptionKeys":
MessageLookupByLibrary.simpleMessage("Encryption keys"),
"endToEndEncrypted":
MessageLookupByLibrary.simpleMessage("end-to-end encrypted"),
"enterCode": MessageLookupByLibrary.simpleMessage("Enter code"),
"enterEmail": MessageLookupByLibrary.simpleMessage("Enter email"),
"enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Enter a new password we can use to encrypt your data"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Enter a password we can use to encrypt your data"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(
"Enter the 6-digit code from\nyour authenticator app"),
"enterValidEmail": MessageLookupByLibrary.simpleMessage( "enterValidEmail": MessageLookupByLibrary.simpleMessage(
"Please enter a valid email address."), "Please enter a valid email address."),
"enterYourEmailAddress": "enterYourEmailAddress":
MessageLookupByLibrary.simpleMessage("Enter your email address"), MessageLookupByLibrary.simpleMessage("Enter your email address"),
"enterYourPassword":
MessageLookupByLibrary.simpleMessage("Enter your password"),
"enterYourRecoveryKey":
MessageLookupByLibrary.simpleMessage("Enter your recovery key"),
"feedback": MessageLookupByLibrary.simpleMessage("Feedback"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Forgot password"),
"fromYourRegisteredEmailAddress": MessageLookupByLibrary.simpleMessage(
"from your registered email address."),
"generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage(
"Generating encryption keys..."),
"howItWorks": MessageLookupByLibrary.simpleMessage("How it works"),
"incorrectPasswordTitle":
MessageLookupByLibrary.simpleMessage("Incorrect password"),
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(
"The recovery key you entered is incorrect"),
"incorrectRecoveryKeyTitle":
MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
"insecureDevice":
MessageLookupByLibrary.simpleMessage("Insecure device"),
"invalidEmailAddress": "invalidEmailAddress":
MessageLookupByLibrary.simpleMessage("Invalid email address"), MessageLookupByLibrary.simpleMessage("Invalid email address"),
"verify": MessageLookupByLibrary.simpleMessage("Verify") "invalidKey": MessageLookupByLibrary.simpleMessage("Invalid key"),
"invalidRecoveryKey": MessageLookupByLibrary.simpleMessage(
"The recovery key you entered is not valid. Please make sure it "),
"kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
"Kindly help us with this information"),
"logInLabel": MessageLookupByLibrary.simpleMessage("Log in"),
"lostDevice": MessageLookupByLibrary.simpleMessage("Lost device?"),
"manage": MessageLookupByLibrary.simpleMessage("Manage"),
"moderateStrength": MessageLookupByLibrary.simpleMessage("Moderate"),
"noPasswordWarningPart1": MessageLookupByLibrary.simpleMessage(
"We don\'t store this password, so if you forget,"),
"noPasswordWarningPart2":
MessageLookupByLibrary.simpleMessage("we cannot decrypt your data"),
"noRecoveryKey":
MessageLookupByLibrary.simpleMessage("No recovery key?"),
"noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage(
"Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key"),
"ok": MessageLookupByLibrary.simpleMessage("Ok"),
"oops": MessageLookupByLibrary.simpleMessage("Oops"),
"orPickAnExistingOne":
MessageLookupByLibrary.simpleMessage("Or pick an existing one"),
"password": MessageLookupByLibrary.simpleMessage("Password"),
"passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage(
"Password changed successfully"),
"passwordStrength": m2,
"pleaseSendAnEmailTo":
MessageLookupByLibrary.simpleMessage("Please send an email to"),
"pleaseTryAgain":
MessageLookupByLibrary.simpleMessage("Please try again"),
"pleaseWait": MessageLookupByLibrary.simpleMessage("Please wait..."),
"privacyPolicy": MessageLookupByLibrary.simpleMessage("privacy policy"),
"privacyPolicyTitle":
MessageLookupByLibrary.simpleMessage("Privacy Policy"),
"recover": MessageLookupByLibrary.simpleMessage("Recover"),
"recoverAccount":
MessageLookupByLibrary.simpleMessage("Recover account"),
"recoverButton": MessageLookupByLibrary.simpleMessage("Recover"),
"recoveryKey": MessageLookupByLibrary.simpleMessage("Recovery key"),
"recoveryKeyCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
"Recovery key copied to clipboard"),
"recoveryKeyOnForgotPassword": MessageLookupByLibrary.simpleMessage(
"If you forget your password, the only way you can recover your data is with this key."),
"recoveryKeySaveDescription": MessageLookupByLibrary.simpleMessage(
"We don\'t store this key, please save this 24 word key in a safe place."),
"recoveryKeySuccessBody": MessageLookupByLibrary.simpleMessage(
"Great! Your recovery key is valid. Thank you for verifying.\n\nPlease remember to keep your recovery key safely backed up."),
"recoveryKeyVerified":
MessageLookupByLibrary.simpleMessage("Recovery key verified"),
"recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage(
"Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly."),
"recoverySuccessful":
MessageLookupByLibrary.simpleMessage("Recovery successful!"),
"recreatePasswordBody": MessageLookupByLibrary.simpleMessage(
"The current device is not powerful enough to verify your "),
"recreatePasswordTitle":
MessageLookupByLibrary.simpleMessage("Recreate password"),
"remove": MessageLookupByLibrary.simpleMessage("Remove"),
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Remove participant"),
"resendEmail": MessageLookupByLibrary.simpleMessage("Resend email"),
"resetPasswordTitle":
MessageLookupByLibrary.simpleMessage("Reset password"),
"saveKey": MessageLookupByLibrary.simpleMessage("Save key"),
"saveYourRecoveryKeyIfYouHaventAlready":
MessageLookupByLibrary.simpleMessage(
"Save your recovery key if you haven\'t already"),
"scanCode": MessageLookupByLibrary.simpleMessage("Scan code"),
"scanThisBarcodeWithnyourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(
"Scan this barcode with\nyour authenticator app"),
"selectReason": MessageLookupByLibrary.simpleMessage("Select reason"),
"sendEmail": MessageLookupByLibrary.simpleMessage("Send email"),
"setPasswordTitle":
MessageLookupByLibrary.simpleMessage("Set password"),
"setupComplete": MessageLookupByLibrary.simpleMessage("Setup complete"),
"somethingWentWrongPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Something went wrong, please try again"),
"sorry": MessageLookupByLibrary.simpleMessage("Sorry"),
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease":
MessageLookupByLibrary.simpleMessage(
"Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device."),
"strongStrength": MessageLookupByLibrary.simpleMessage("Strong"),
"tapToCopy": MessageLookupByLibrary.simpleMessage("tap to copy"),
"tapToEnterCode":
MessageLookupByLibrary.simpleMessage("Tap to enter code"),
"terminate": MessageLookupByLibrary.simpleMessage("Terminate"),
"terminateSession":
MessageLookupByLibrary.simpleMessage("Terminate session?"),
"termsAgreePart1":
MessageLookupByLibrary.simpleMessage("I agree to the "),
"termsOfService":
MessageLookupByLibrary.simpleMessage("terms of service"),
"termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Terms"),
"thisCanBeUsedToRecoverYourAccountIfYou":
MessageLookupByLibrary.simpleMessage(
"This can be used to recover your account if you lose your second factor"),
"thisDevice": MessageLookupByLibrary.simpleMessage("This device"),
"thisWillLogYouOutOfTheFollowingDevice":
MessageLookupByLibrary.simpleMessage(
"This will log you out of the following device:"),
"thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage(
"This will log you out of this device!"),
"tryAgain": MessageLookupByLibrary.simpleMessage("Try again"),
"twofactorAuthenticationPageTitle":
MessageLookupByLibrary.simpleMessage("Two-factor authentication"),
"twofactorSetup":
MessageLookupByLibrary.simpleMessage("Two-factor setup"),
"useRecoveryKey":
MessageLookupByLibrary.simpleMessage("Use recovery key"),
"verify": MessageLookupByLibrary.simpleMessage("Verify"),
"verifyEmail": MessageLookupByLibrary.simpleMessage("Verify email"),
"verifyPassword":
MessageLookupByLibrary.simpleMessage("Verify password"),
"verifyingRecoveryKey":
MessageLookupByLibrary.simpleMessage("Verifying recovery key..."),
"viewRecoveryKey":
MessageLookupByLibrary.simpleMessage("View recovery key"),
"viewer": MessageLookupByLibrary.simpleMessage("Viewer"),
"weakStrength": MessageLookupByLibrary.simpleMessage("Weak"),
"welcomeBack": MessageLookupByLibrary.simpleMessage("Welcome back!"),
"weveSentAMailTo":
MessageLookupByLibrary.simpleMessage("We\'ve sent a mail to"),
"yesConvertToViewer":
MessageLookupByLibrary.simpleMessage("Yes, convert to viewer"),
"you": MessageLookupByLibrary.simpleMessage("You"),
"yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage(
"Your account has been deleted")
}; };
} }

File diff suppressed because it is too large Load diff

View file

@ -5,5 +5,197 @@
"cancel": "Cancel", "cancel": "Cancel",
"verify": "Verify", "verify": "Verify",
"invalidEmailAddress": "Invalid email address", "invalidEmailAddress": "Invalid email address",
"enterValidEmail": "Please enter a valid email address." "enterValidEmail": "Please enter a valid email address.",
"deleteAccount": "Delete account",
"askDeleteReason": "What is the main reason you are deleting your account?",
"deleteAccountFeedbackPrompt": "We are sorry to see you go. Please share your feedback to help us improve.",
"feedback": "Feedback",
"kindlyHelpUsWithThisInformation": "Kindly help us with this information",
"confirmDeletePrompt": "Yes, I want to permanently delete this account and all its data.",
"confirmAccountDeletion": "Confirm Account Deletion",
"deleteConfirmDialogBody": "You are about to permanently delete your account and all its data.\nThis action is irreversible.",
"deleteAccountPermanentlyButton": "Delete Account Permanently",
"yourAccountHasBeenDeleted": "Your account has been deleted",
"selectReason": "Select reason",
"deleteReason1": "Its missing a key feature that I need",
"deleteReason2": "The app or a certain feature does not \nbehave as I think it should",
"deleteReason3": "I found another service that I like better",
"deleteReason4": "My reason isnt listed",
"sendEmail": "Send email",
"deleteRequestSLAText": "Your request will be processed within 72 hours.",
"pleaseSendAnEmailTo": "Please send an email to",
"@pleaseSendAnEmailTo": {
"description": "This text is part of the sentence 'Please send an email to email@ente.io from your registered email address.'"
},
"fromYourRegisteredEmailAddress": "from your registered email address.",
"@fromYourRegisteredEmailAddress": {
"description": "This text is part of the sentence 'Please send an email to email@ente.io from your registered email address.'"
},
"ok": "Ok",
"createAccount": "Create account",
"createNewAccount": "Create new account",
"password": "Password",
"confirmPassword": "Confirm password",
"activeSessions": "Active sessions",
"oops": "Oops",
"somethingWentWrongPleaseTryAgain": "Something went wrong, please try again",
"thisWillLogYouOutOfThisDevice": "This will log you out of this device!",
"thisWillLogYouOutOfTheFollowingDevice": "This will log you out of the following device:",
"terminateSession": "Terminate session?",
"terminate": "Terminate",
"thisDevice": "This device",
"recoverButton": "Recover",
"recoverySuccessful": "Recovery successful!",
"decrypting": "Decrypting...",
"incorrectRecoveryKeyTitle": "Incorrect recovery key",
"incorrectRecoveryKeyBody": "The recovery key you entered is incorrect",
"forgotPassword": "Forgot password",
"enterYourRecoveryKey": "Enter your recovery key",
"noRecoveryKey": "No recovery key?",
"sorry": "Sorry",
"noRecoveryKeyNoDecryption": "Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key",
"verifyEmail": "Verify email",
"checkInboxAndSpamFolder": "Please check your inbox (and spam) to complete verification",
"tapToEnterCode": "Tap to enter code",
"resendEmail": "Resend email",
"weveSentAMailTo": "We've sent a mail to",
"setPasswordTitle": "Set password",
"changePasswordTitle": "Change password",
"resetPasswordTitle": "Reset password",
"encryptionKeys": "Encryption keys",
"noPasswordWarningPart1": "We don't store this password, so if you forget,",
"@noPasswordWarningPart1": {
"description": "This text is part1 the sentence 'We don't store this password, so if you forget, we cannot decrypt your data.'"
},
"noPasswordWarningPart2": "we cannot decrypt your data",
"@noPasswordWarningPart2": {
"description": "This text is part2 the sentence 'We don't store this password, so if you forget, we cannot decrypt your data.'"
},
"enterPasswordToEncrypt": "Enter a password we can use to encrypt your data",
"enterNewPasswordToEncrypt": "Enter a new password we can use to encrypt your data",
"weakStrength": "Weak",
"strongStrength": "Strong",
"moderateStrength": "Moderate",
"passwordStrength": "Password strength: {passwordStrengthValue}",
"@passwordStrength": {
"description": "Text to indicate the password strength",
"placeholders": {
"passwordStrengthValue": {
"description": "The strength of the password as a string",
"type": "String",
"example": "Weak or Moderate or Strong"
}
},
"message": "Password Strength: {passwordStrengthText}"
},
"passwordChangedSuccessfully": "Password changed successfully",
"generatingEncryptionKeys": "Generating encryption keys...",
"pleaseWait": "Please wait...",
"continueLabel": "Continue",
"insecureDevice": "Insecure device",
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.",
"howItWorks": "How it works",
"encryption": "Encryption",
"ackPasswordLostWarningPart1": "I understand that if I lose my password, I may lose my data since my data is ",
"endToEndEncrypted": "end-to-end encrypted",
"ackPasswordLostWarningPart2": " with ente",
"@ackPasswordLostWarningPart2": {
"description": "This text is part2 the sentence 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted with ente.'"
},
"privacyPolicyTitle": "Privacy Policy",
"termsOfServicesTitle": "Terms",
"termsAgreePart1": "I agree to the ",
"@termsAgreePart1": {
"description": "Note: there's a trailing space. This text is part the sentence 'I agree to the terms of service and privacy policy.'"
},
"privacyPolicy": "privacy policy",
"and": "and",
"@and": {
"description": "Separator used in sentences like 'I agree to the terms of service and privacy policy.'"
},
"termsOfService": "terms of service",
"logInLabel": "Log in",
"byClickingLogInIAgreeToThe": "By clicking log in, I agree to the",
"@byClickingLogInIAgreeToThe": {
"description": "This text is part the sentence 'By clicking log in, I agree to the terms of service and privacy policy'"
},
"changeEmail": "Change email",
"enterYourPassword": "Enter your password",
"welcomeBack": "Welcome back!",
"contactSupport": "Contact support",
"incorrectPasswordTitle": "Incorrect password",
"pleaseTryAgain": "Please try again",
"recreatePasswordTitle": "Recreate password",
"useRecoveryKey": "Use recovery key",
"recreatePasswordBody": "The current device is not powerful enough to verify your ",
"verifyPassword": "Verify password",
"recoveryKey": "Recovery key",
"recoveryKeyOnForgotPassword": "If you forget your password, the only way you can recover your data is with this key.",
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
"doThisLater": "Do this later",
"saveKey": "Save key",
"recoveryKeyCopiedToClipboard": "Recovery key copied to clipboard",
"recoverAccount": "Recover account",
"recover": "Recover",
"dropSupportEmail": "Please drop an email to {supportEmail} from your registered email address",
"@dropSupportEmail": {
"placeholders": {
"supportEmail": {
"description": "The support email address",
"type": "String",
"example": "support@ente.io"
}
}
},
"twofactorSetup": "Two-factor setup",
"enterCode": "Enter code",
"scanCode": "Scan code",
"codeCopiedToClipboard": "Code copied to clipboard",
"copypasteThisCodentoYourAuthenticatorApp": "Copy-paste this code\nto your authenticator app",
"tapToCopy": "tap to copy",
"scanThisBarcodeWithnyourAuthenticatorApp": "Scan this barcode with\nyour authenticator app",
"enterThe6digitCodeFromnyourAuthenticatorApp": "Enter the 6-digit code from\nyour authenticator app",
"confirm": "Confirm",
"setupComplete": "Setup complete",
"saveYourRecoveryKeyIfYouHaventAlready": "Save your recovery key if you haven't already",
"thisCanBeUsedToRecoverYourAccountIfYou": "This can be used to recover your account if you lose your second factor",
"twofactorAuthenticationPageTitle": "Two-factor authentication",
"lostDevice": "Lost device?",
"verifyingRecoveryKey": "Verifying recovery key...",
"recoveryKeyVerified": "Recovery key verified",
"recoveryKeySuccessBody": "Great! Your recovery key is valid. Thank you for verifying.\n\nPlease remember to keep your recovery key safely backed up.",
"invalidRecoveryKey": "The recovery key you entered is not valid. Please make sure it ",
"invalidKey": "Invalid key",
"tryAgain": "Try again",
"viewRecoveryKey": "View recovery key",
"confirmRecoveryKey": "Confirm recovery key",
"recoveryKeyVerifyReason": "Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly.",
"confirmYourRecoveryKey": "Confirm your recovery key",
"addViewer": "Add viewer",
"addCollaborator": "Add collaborator",
"addANewEmail": "Add a new email",
"orPickAnExistingOne": "Or pick an existing one",
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Collaborators can add photos and videos to the shared album.",
"enterEmail": "Enter email",
"albumOwner": "Owner",
"@albumOwner": {
"description": "Role of the album owner"
},
"you": "You",
"collaborator": "Collaborator",
"addMore": "Add more",
"@addMore": {
"description": "Button text to add more collaborators/viewers"
},
"viewer": "Viewer",
"remove": "Remove",
"removeParticipant": "Remove participant",
"@removeParticipant": {
"description": "menuSectionTitle for removing a participant"
},
"manage": "Manage",
"addedAs": "Added as",
"changePermissions": "Change permissions?",
"yesConvertToViewer": "Yes, convert to viewer",
"cannotAddMorePhotosAfterBecomingViewer": "{user} will not be able to add more photos to this album\n\nThey will still be able to remove existing photos added by them"
} }

View file

@ -4,6 +4,7 @@ import "package:dropdown_button2/dropdown_button2.dart";
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:logging/logging.dart"; import "package:logging/logging.dart";
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/delete_account.dart'; import 'package:photos/models/delete_account.dart';
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/theme/ente_theme.dart'; import 'package:photos/theme/ente_theme.dart';
@ -26,23 +27,26 @@ class DeleteAccountPage extends StatefulWidget {
class _DeleteAccountPageState extends State<DeleteAccountPage> { class _DeleteAccountPageState extends State<DeleteAccountPage> {
bool _hasConfirmedDeletion = false; bool _hasConfirmedDeletion = false;
final _feedbackTextCtrl = TextEditingController(); final _feedbackTextCtrl = TextEditingController();
final String _defaultSelection = 'Select reason'; late String _defaultSelection = S.of(context).selectReason;
late String _dropdownValue = _defaultSelection; String? _dropdownValue;
late final List<String> _deletionReason = [ late final List<String> _deletionReason = [
_defaultSelection, _defaultSelection,
'Its missing a key feature that I need', S.of(context).deleteReason1,
'The app or a certain feature does not \nbehave as I think it should', S.of(context).deleteReason2,
'I found another service that I like better', S.of(context).deleteReason3,
'My reason isnt listed', S.of(context).deleteReason4,
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_defaultSelection = S.of(context).selectReason;
_dropdownValue ??= _defaultSelection;
final colorScheme = getEnteColorScheme(context); final colorScheme = getEnteColorScheme(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
title: const Text("Delete account"), title: Text(S.of(context).deleteAccount),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
color: Theme.of(context).iconTheme.color, color: Theme.of(context).iconTheme.color,
@ -59,7 +63,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Text(
"What is the main reason you are deleting your account?", S.of(context).askDeleteReason,
style: getEnteTextTheme(context).body, style: getEnteTextTheme(context).body,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -97,8 +101,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
"We are sorry to see you go. Please share your feedback to " S.of(context).deleteAccountFeedbackPrompt,
"help us improve.",
style: getEnteTextTheme(context).body, style: getEnteTextTheme(context).body,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -116,7 +119,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
), ),
filled: true, filled: true,
fillColor: Colors.transparent, fillColor: Colors.transparent,
hintText: "Feedback", hintText: S.of(context).feedback,
contentPadding: const EdgeInsets.all(12), contentPadding: const EdgeInsets.all(12),
), ),
controller: _feedbackTextCtrl, controller: _feedbackTextCtrl,
@ -135,7 +138,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 4.0), padding: const EdgeInsets.only(top: 4.0),
child: Text( child: Text(
"Kindly help us with this information", S.of(context).kindlyHelpUsWithThisInformation,
style: getEnteTextTheme(context) style: getEnteTextTheme(context)
.smallBold .smallBold
.copyWith(color: colorScheme.warning700), .copyWith(color: colorScheme.warning700),
@ -162,8 +165,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
), ),
Expanded( Expanded(
child: Text( child: Text(
"Yes, I want to permanently delete this account and " S.of(context).confirmDeletePrompt,
"all its data.",
style: getEnteTextTheme(context).bodyMuted, style: getEnteTextTheme(context).bodyMuted,
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
@ -177,7 +179,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
children: [ children: [
ButtonWidget( ButtonWidget(
buttonType: ButtonType.critical, buttonType: ButtonType.critical,
labelText: "Confirm Account Deletion", labelText: S.of(context).confirmAccountDeletion,
isDisabled: _shouldBlockDeletion(), isDisabled: _shouldBlockDeletion(),
onTap: () async { onTap: () async {
await _initiateDelete(context); await _initiateDelete(context);
@ -187,7 +189,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
ButtonWidget( ButtonWidget(
buttonType: ButtonType.secondary, buttonType: ButtonType.secondary,
labelText: "Cancel", labelText: S.of(context).cancel,
onTap: () async { onTap: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -220,10 +222,9 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
Future<void> _initiateDelete(BuildContext context) async { Future<void> _initiateDelete(BuildContext context) async {
final choice = await showChoiceDialog( final choice = await showChoiceDialog(
context, context,
title: "Confirm Account Deletion", title: S.of(context).confirmAccountDeletion,
body: "You are about to permanently delete your account and all its data." body: S.of(context).deleteConfirmDialogBody,
"\nThis action is irreversible.", firstButtonLabel: S.of(context).deleteAccountPermanentlyButton,
firstButtonLabel: "Delete Account Permanently",
firstButtonType: ButtonType.critical, firstButtonType: ButtonType.critical,
firstButtonOnTap: () async { firstButtonOnTap: () async {
final deleteChallengeResponse = final deleteChallengeResponse =
@ -260,11 +261,11 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
await UserService.instance.deleteAccount( await UserService.instance.deleteAccount(
context, context,
challengeResponseStr, challengeResponseStr,
reasonCategory: _dropdownValue, reasonCategory: _dropdownValue!,
feedback: _feedbackTextCtrl.text.trim(), feedback: _feedbackTextCtrl.text.trim(),
); );
Navigator.of(context).popUntil((route) => route.isFirst); Navigator.of(context).popUntil((route) => route.isFirst);
showShortToast(context, "Your account has been deleted"); showShortToast(context, S.of(context).yourAccountHasBeenDeleted);
} catch (e, s) { } catch (e, s) {
Logger("DeleteAccount").severe("failed to delete", e, s); Logger("DeleteAccount").severe("failed to delete", e, s);
showGenericErrorDialog(context: context); showGenericErrorDialog(context: context);
@ -273,17 +274,17 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
Future<void> _requestEmailForDeletion(BuildContext context) async { Future<void> _requestEmailForDeletion(BuildContext context) async {
final AlertDialog alert = AlertDialog( final AlertDialog alert = AlertDialog(
title: const Text( title: Text(
"Delete account", S.of(context).deleteAccount,
style: TextStyle( style: const TextStyle(
color: Colors.red, color: Colors.red,
), ),
), ),
content: RichText( content: RichText(
text: TextSpan( text: TextSpan(
children: [ children: [
const TextSpan( TextSpan(
text: "Please send an email to ", text: S.of(context).pleaseSendAnEmailTo,
), ),
TextSpan( TextSpan(
text: "account-deletion@ente.io", text: "account-deletion@ente.io",
@ -291,9 +292,9 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
color: Colors.orange[300], color: Colors.orange[300],
), ),
), ),
const TextSpan( TextSpan(
text: text:
" from your registered email address.\n\nYour request will be processed within 72 hours.", " ${S.of(context).fromYourRegisteredEmailAddress}\n\n${S.of(context).deleteRequestSLAText}",
), ),
], ],
style: TextStyle( style: TextStyle(
@ -305,9 +306,9 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
), ),
actions: [ actions: [
TextButton( TextButton(
child: const Text( child: Text(
"Send email", S.of(context).sendEmail,
style: TextStyle( style: const TextStyle(
color: Colors.red, color: Colors.red,
), ),
), ),
@ -316,13 +317,13 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
await sendEmail( await sendEmail(
context, context,
to: 'account-deletion@ente.io', to: 'account-deletion@ente.io',
subject: '[Delete account]', subject: '[${S.of(context).deleteAccount}]',
); );
}, },
), ),
TextButton( TextButton(
child: Text( child: Text(
"Ok", S.of(context).ok,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
), ),

View file

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:password_strength/password_strength.dart'; import 'package:password_strength/password_strength.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:photos/ui/common/web_page.dart'; import 'package:photos/ui/common/web_page.dart';
@ -99,7 +100,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _isFormValid(), isFormValid: _isFormValid(),
buttonText: 'Create account', buttonText: S.of(context).createAccount,
onPressedFunction: () { onPressedFunction: () {
_config.setVolatilePassword(_passwordController1.text); _config.setVolatilePassword(_passwordController1.text);
UserService.instance.setEmail(_email!); UserService.instance.setEmail(_email!);
@ -114,13 +115,13 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
} }
Widget _getBody() { Widget _getBody() {
var passwordStrengthText = 'Weak'; var passwordStrengthText = S.of(context).weakStrength;
var passwordStrengthColor = Colors.redAccent; var passwordStrengthColor = Colors.redAccent;
if (_passwordStrength > kStrongPasswordStrengthThreshold) { if (_passwordStrength > kStrongPasswordStrengthThreshold) {
passwordStrengthText = 'Strong'; passwordStrengthText = S.of(context).strongStrength;
passwordStrengthColor = Colors.greenAccent; passwordStrengthColor = Colors.greenAccent;
} else if (_passwordStrength > kMildPasswordStrengthThreshold) { } else if (_passwordStrength > kMildPasswordStrengthThreshold) {
passwordStrengthText = 'Moderate'; passwordStrengthText = S.of(context).moderateStrength;
passwordStrengthColor = Colors.orangeAccent; passwordStrengthColor = Colors.orangeAccent;
} }
return Column( return Column(
@ -133,7 +134,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
padding: padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20), const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text( child: Text(
'Create new account', S.of(context).createNewAccount,
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
), ),
@ -145,7 +146,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
decoration: InputDecoration( decoration: InputDecoration(
fillColor: _emailIsValid ? _validFieldValueColor : null, fillColor: _emailIsValid ? _validFieldValueColor : null,
filled: true, filled: true,
hintText: 'Email', hintText: S.of(context).email,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 14, vertical: 14,
@ -193,7 +194,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
fillColor: fillColor:
_passwordIsValid ? _validFieldValueColor : null, _passwordIsValid ? _validFieldValueColor : null,
filled: true, filled: true,
hintText: "Password", hintText: S.of(context).password,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 14, vertical: 14,
@ -262,7 +263,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
? _validFieldValueColor ? _validFieldValueColor
: null, : null,
filled: true, filled: true,
hintText: "Confirm password", hintText: S.of(context).confirmPassword,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 14, vertical: 14,
@ -314,7 +315,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 8), const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
child: Text( child: Text(
'Password strength: $passwordStrengthText', S.of(context).passwordStrength(passwordStrengthText),
style: TextStyle( style: TextStyle(
color: passwordStrengthColor, color: passwordStrengthColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -371,11 +372,11 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
children: [ children: [
const TextSpan( TextSpan(
text: "I agree to the ", text: S.of(context).termsAgreePart1,
), ),
TextSpan( TextSpan(
text: "terms of service", text: S.of(context).termsOfService,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -384,8 +385,8 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"Terms", S.of(context).termsOfServicesTitle,
"https://ente.io/terms", "https://ente.io/terms",
); );
}, },
@ -393,9 +394,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
); );
}, },
), ),
const TextSpan(text: " and "), TextSpan(text: ' ${S.of(context).and} '),
TextSpan( TextSpan(
text: "privacy policy", text: S.of(context).privacyPolicy,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -404,8 +405,8 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"Privacy", S.of(context).privacyPolicyTitle,
"https://ente.io/privacy", "https://ente.io/privacy",
); );
}, },
@ -450,12 +451,11 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
children: [ children: [
const TextSpan( TextSpan(
text: text: S.of(context).ackPasswordLostWarningPart1,
"I understand that if I lose my password, I may lose my data since my data is ",
), ),
TextSpan( TextSpan(
text: "end-to-end encrypted", text: S.of(context).endToEndEncrypted,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -464,8 +464,8 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"Encryption", S.of(context).encryption,
"https://ente.io/architecture", "https://ente.io/architecture",
); );
}, },
@ -473,7 +473,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
); );
}, },
), ),
const TextSpan(text: " with ente"), TextSpan(
text: " ${S.of(context).ackPasswordLostWarningPart2}",
),
], ],
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme

View file

@ -2,6 +2,7 @@ import 'package:email_validator/email_validator.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/l10n/l10n.dart"; import "package:photos/l10n/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
@ -54,7 +55,7 @@ class _LoginPageState extends State<LoginPage> {
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _emailIsValid, isFormValid: _emailIsValid,
buttonText: 'Log in', buttonText: S.of(context).logInLabel,
onPressedFunction: () { onPressedFunction: () {
UserService.instance.setEmail(_email!); UserService.instance.setEmail(_email!);
UserService.instance UserService.instance
@ -148,11 +149,12 @@ class _LoginPageState extends State<LoginPage> {
.subtitle1! .subtitle1!
.copyWith(fontSize: 12), .copyWith(fontSize: 12),
children: [ children: [
const TextSpan( TextSpan(
text: "By clicking log in, I agree to the ", text: S.of(context).byClickingLogInIAgreeToThe +
" ",
), ),
TextSpan( TextSpan(
text: "terms of service", text: S.of(context).termsOfService,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -161,8 +163,8 @@ class _LoginPageState extends State<LoginPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"terms", S.of(context).termsOfServicesTitle,
"https://ente.io/terms", "https://ente.io/terms",
); );
}, },
@ -170,9 +172,9 @@ class _LoginPageState extends State<LoginPage> {
); );
}, },
), ),
const TextSpan(text: " and "), TextSpan(text: " ${S.of(context).and} "),
TextSpan( TextSpan(
text: "privacy policy", text: S.of(context).privacyPolicy,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -181,8 +183,8 @@ class _LoginPageState extends State<LoginPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"privacy", S.of(context).privacyPolicyTitle,
"https://ente.io/privacy", "https://ente.io/privacy",
); );
}, },

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:step_progress_indicator/step_progress_indicator.dart'; import 'package:step_progress_indicator/step_progress_indicator.dart';
@ -64,7 +65,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _verificationCodeController.text.isNotEmpty, isFormValid: _verificationCodeController.text.isNotEmpty,
buttonText: 'Verify', buttonText: S.of(context).verify,
onPressedFunction: () { onPressedFunction: () {
if (widget.isChangeEmail) { if (widget.isChangeEmail) {
UserService.instance.changeEmail( UserService.instance.changeEmail(
@ -93,7 +94,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 15), padding: const EdgeInsets.fromLTRB(20, 30, 20, 15),
child: Text( child: Text(
'Verify email', S.of(context).verifyEmail,
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
), ),
@ -114,7 +115,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
.subtitle1! .subtitle1!
.copyWith(fontSize: 14), .copyWith(fontSize: 14),
children: [ children: [
const TextSpan(text: "We've sent a mail to "), TextSpan(text: S.of(context).weveSentAMailTo),
TextSpan( TextSpan(
text: widget.email, text: widget.email,
style: TextStyle( style: TextStyle(
@ -128,7 +129,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
), ),
), ),
Text( Text(
'Please check your inbox (and spam) to complete verification', S.of(context).checkInboxAndSpamFolder,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.subtitle1! .subtitle1!
@ -150,7 +151,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.subtitle1,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
hintText: 'Tap to enter code', hintText: S.of(context).tapToEnterCode,
contentPadding: const EdgeInsets.all(15), contentPadding: const EdgeInsets.all(15),
border: UnderlineInputBorder( border: UnderlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -183,7 +184,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
); );
}, },
child: Text( child: Text(
"Resend email", S.of(context).resendEmail,
style: Theme.of(context).textTheme.subtitle1!.copyWith( style: Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,

View file

@ -6,6 +6,7 @@ import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/events/account_configured_event.dart'; import 'package:photos/events/account_configured_event.dart';
import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/events/subscription_purchased_event.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/account/recovery_key_page.dart'; import 'package:photos/ui/account/recovery_key_page.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
@ -87,13 +88,13 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} }
} }
String title = "Set password"; String title = S.of(context).setPasswordTitle;
if (widget.mode == PasswordEntryMode.update) { if (widget.mode == PasswordEntryMode.update) {
title = "Change password"; title = S.of(context).changePasswordTitle;
} else if (widget.mode == PasswordEntryMode.reset) { } else if (widget.mode == PasswordEntryMode.reset) {
title = "Reset password"; title = S.of(context).resetPasswordTitle;
} else if (_volatilePassword != null) { } else if (_volatilePassword != null) {
title = "Encryption keys"; title = S.of(context).encryptionKeys;
} }
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: isKeypadOpen, resizeToAvoidBottomInset: isKeypadOpen,
@ -130,13 +131,13 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Widget _getBody(String buttonTextAndHeading) { Widget _getBody(String buttonTextAndHeading) {
final email = Configuration.instance.getEmail(); final email = Configuration.instance.getEmail();
var passwordStrengthText = 'Weak'; var passwordStrengthText = S.of(context).weakStrength;
var passwordStrengthColor = Colors.redAccent; var passwordStrengthColor = Colors.redAccent;
if (_passwordStrength > kStrongPasswordStrengthThreshold) { if (_passwordStrength > kStrongPasswordStrengthThreshold) {
passwordStrengthText = 'Strong'; passwordStrengthText = S.of(context).strongStrength;
passwordStrengthColor = Colors.greenAccent; passwordStrengthColor = Colors.greenAccent;
} else if (_passwordStrength > kMildPasswordStrengthThreshold) { } else if (_passwordStrength > kMildPasswordStrengthThreshold) {
passwordStrengthText = 'Moderate'; passwordStrengthText = S.of(context).moderateStrength;
passwordStrengthColor = Colors.orangeAccent; passwordStrengthColor = Colors.orangeAccent;
} }
if (_volatilePassword != null) { if (_volatilePassword != null) {
@ -159,9 +160,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text( child: Text(
"Enter a" + widget.mode == PasswordEntryMode.set
(widget.mode != PasswordEntryMode.set ? " new " : " ") + ? S.of(context).enterPasswordToEncrypt
"password we can use to encrypt your data", : S.of(context).enterNewPasswordToEncrypt,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
@ -179,12 +180,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
.subtitle1! .subtitle1!
.copyWith(fontSize: 14), .copyWith(fontSize: 14),
children: [ children: [
const TextSpan( TextSpan(
text: text: '${S.of(context).noPasswordWarningPart1} ',
"We don't store this password, so if you forget, ",
), ),
TextSpan( TextSpan(
text: "we cannot decrypt your data", text: S.of(context).noPasswordWarningPart2,
style: style:
Theme.of(context).textTheme.subtitle1!.copyWith( Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,
@ -218,7 +218,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
fillColor: fillColor:
_isPasswordValid ? _validFieldValueColor : null, _isPasswordValid ? _validFieldValueColor : null,
filled: true, filled: true,
hintText: "Password", hintText: S.of(context).password,
contentPadding: const EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder( border: UnderlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -281,7 +281,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
decoration: InputDecoration( decoration: InputDecoration(
fillColor: _passwordsMatch ? _validFieldValueColor : null, fillColor: _passwordsMatch ? _validFieldValueColor : null,
filled: true, filled: true,
hintText: "Confirm password", hintText: S.of(context).confirmPassword,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
vertical: 20, vertical: 20,
@ -335,7 +335,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
padding: padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 8), const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: Text( child: Text(
'Password Strength: $passwordStrengthText', S.of(context).passwordStrength(passwordStrengthText),
style: TextStyle( style: TextStyle(
color: passwordStrengthColor, color: passwordStrengthColor,
), ),
@ -349,8 +349,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const WebPage( return WebPage(
"How it works", S.of(context).howItWorks,
"https://ente.io/architecture", "https://ente.io/architecture",
); );
}, },
@ -361,7 +361,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: "How it works", text: S.of(context).howItWorks,
style: Theme.of(context).textTheme.subtitle1!.copyWith( style: Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
@ -381,14 +381,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
void _updatePassword() async { void _updatePassword() async {
final dialog = final dialog =
createProgressDialog(context, "Generating encryption keys..."); createProgressDialog(context, S.of(context).generatingEncryptionKeys);
await dialog.show(); await dialog.show();
try { try {
final keyAttributes = await Configuration.instance final keyAttributes = await Configuration.instance
.updatePassword(_passwordController1.text); .updatePassword(_passwordController1.text);
await UserService.instance.updateKeyAttributes(keyAttributes); await UserService.instance.updateKeyAttributes(keyAttributes);
await dialog.hide(); await dialog.hide();
showShortToast(context, "Password changed successfully"); showShortToast(context, S.of(context).passwordChangedSuccessfully);
Navigator.of(context).pop(); Navigator.of(context).pop();
if (widget.mode == PasswordEntryMode.reset) { if (widget.mode == PasswordEntryMode.reset) {
Bus.instance.fire(SubscriptionPurchasedEvent()); Bus.instance.fire(SubscriptionPurchasedEvent());
@ -403,14 +403,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Future<void> _showRecoveryCodeDialog(String password) async { Future<void> _showRecoveryCodeDialog(String password) async {
final dialog = final dialog =
createProgressDialog(context, "Generating encryption keys..."); createProgressDialog(context, S.of(context).generatingEncryptionKeys);
await dialog.show(); await dialog.show();
try { try {
final result = await Configuration.instance.generateKey(password); final result = await Configuration.instance.generateKey(password);
Configuration.instance.setVolatilePassword(null); Configuration.instance.setVolatilePassword(null);
await dialog.hide(); await dialog.hide();
onDone() async { onDone() async {
final dialog = createProgressDialog(context, "Please wait..."); final dialog = createProgressDialog(context, S.of(context).pleaseWait);
await dialog.show(); await dialog.show();
try { try {
await UserService.instance.setAttributes(result); await UserService.instance.setAttributes(result);
@ -435,7 +435,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
context, context,
RecoveryKeyPage( RecoveryKeyPage(
result.privateKeyAttributes.recoveryKey, result.privateKeyAttributes.recoveryKey,
"Continue", S.of(context).continueLabel,
showAppBar: false, showAppBar: false,
isDismissible: false, isDismissible: false,
onDone: onDone, onDone: onDone,
@ -448,8 +448,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
if (e is UnsupportedError) { if (e is UnsupportedError) {
showErrorDialog( showErrorDialog(
context, context,
"Insecure device", S.of(context).insecureDevice,
"Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.", S.of(context).sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
); );
} else { } else {
showGenericErrorDialog(context: context); showGenericErrorDialog(context: context);

View file

@ -6,6 +6,7 @@ import 'package:photos/core/configuration.dart';
import 'package:photos/core/errors.dart'; import 'package:photos/core/errors.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/events/subscription_purchased_event.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/ui/account/recovery_page.dart'; import 'package:photos/ui/account/recovery_page.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:photos/ui/components/buttons/button_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart';
@ -67,10 +68,11 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _passwordController.text.isNotEmpty, isFormValid: _passwordController.text.isNotEmpty,
buttonText: 'Verify password', buttonText: S.of(context).verifyPassword,
onPressedFunction: () async { onPressedFunction: () async {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
final dialog = createProgressDialog(context, "Please wait..."); final dialog =
createProgressDialog(context, S.of(context).pleaseWait);
await dialog.show(); await dialog.show();
try { try {
await Configuration.instance.decryptAndSaveSecrets( await Configuration.instance.decryptAndSaveSecrets(
@ -82,12 +84,9 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
await dialog.hide(); await dialog.hide();
final dialogChoice = await showChoiceDialog( final dialogChoice = await showChoiceDialog(
context, context,
title: "Recreate password", title: S.of(context).recreatePasswordTitle,
body: "The current device is not powerful enough to verify your " body: S.of(context).recreatePasswordBody,
"password, so we need to regenerate it once in a way that " firstButtonLabel: S.of(context).useRecoveryKey,
"works with all devices. \n\nPlease login using your "
"recovery key and regenerate your password (you can use the same one again if you wish).",
firstButtonLabel: "Use recovery key",
); );
if (dialogChoice!.action == ButtonAction.first) { if (dialogChoice!.action == ButtonAction.first) {
Navigator.of(context).push( Navigator.of(context).push(
@ -104,15 +103,15 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
await dialog.hide(); await dialog.hide();
final dialogChoice = await showChoiceDialog( final dialogChoice = await showChoiceDialog(
context, context,
title: "Incorrect password", title: S.of(context).incorrectPasswordTitle,
body: "Please try again", body: S.of(context).pleaseTryAgain,
firstButtonLabel: "Contact Support", firstButtonLabel: S.of(context).contactSupport,
secondButtonLabel: "OK", secondButtonLabel: S.of(context).ok,
); );
if (dialogChoice!.action == ButtonAction.first) { if (dialogChoice!.action == ButtonAction.first) {
await sendLogs( await sendLogs(
context, context,
"Contact support", S.of(context).contactSupport,
"support@ente.io", "support@ente.io",
postShare: () {}, postShare: () {},
); );
@ -149,7 +148,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
padding: padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20), const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text( child: Text(
'Welcome back!', S.of(context).welcomeBack,
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
), ),
@ -172,7 +171,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
child: TextFormField( child: TextFormField(
autofillHints: const [AutofillHints.password], autofillHints: const [AutofillHints.password],
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Enter your password", hintText: S.of(context).enterYourPassword,
filled: true, filled: true,
contentPadding: const EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder( border: UnderlineInputBorder(
@ -234,7 +233,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
}, },
child: Center( child: Center(
child: Text( child: Text(
"Forgot password", S.of(context).forgotPassword,
style: style:
Theme.of(context).textTheme.subtitle1!.copyWith( Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,
@ -246,8 +245,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () async { onTap: () async {
final dialog = final dialog = createProgressDialog(
createProgressDialog(context, "Please wait..."); context, S.of(context).pleaseWait);
await dialog.show(); await dialog.show();
await Configuration.instance.logout(); await Configuration.instance.logout();
await dialog.hide(); await dialog.hide();
@ -256,7 +255,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
}, },
child: Center( child: Center(
child: Text( child: Text(
"Change email", S.of(context).changeEmail,
style: style:
Theme.of(context).textTheme.subtitle1!.copyWith( Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,

View file

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart'; import 'package:photos/core/constants.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/ui/common/gradient_button.dart'; import 'package:photos/ui/common/gradient_button.dart';
import 'package:photos/utils/toast_util.dart'; import 'package:photos/utils/toast_util.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
@ -80,7 +81,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
: widget.showAppBar! : widget.showAppBar!
? AppBar( ? AppBar(
elevation: 0, elevation: 0,
title: Text(widget.title ?? "Recovery key"), title: Text(widget.title ?? S.of(context).recoveryKey),
) )
: null, : null,
body: Padding( body: Padding(
@ -101,7 +102,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
widget.showAppBar! widget.showAppBar!
? const SizedBox.shrink() ? const SizedBox.shrink()
: Text( : Text(
widget.title ?? "Recovery key", widget.title ?? S.of(context).recoveryKey,
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
Padding( Padding(
@ -109,7 +110,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
), ),
Text( Text(
widget.text ?? widget.text ??
"If you forget your password, the only way you can recover your data is with this key.", S.of(context).recoveryKeyOnForgotPassword,
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.subtitle1,
), ),
const Padding(padding: EdgeInsets.only(top: 24)), const Padding(padding: EdgeInsets.only(top: 24)),
@ -136,7 +137,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
); );
showShortToast( showShortToast(
context, context,
"Recovery key copied to clipboard", S.of(context).recoveryKeyCopiedToClipboard,
); );
setState(() { setState(() {
_hasTriedToSave = true; _hasTriedToSave = true;
@ -176,7 +177,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
padding: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.symmetric(vertical: 20),
child: Text( child: Text(
widget.subText ?? widget.subText ??
"We don't store this key, please save this 24 word key in a safe place.", S.of(context).recoveryKeySaveDescription,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.bodyText1,
), ),
), ),
@ -212,7 +213,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
onPressed: () async { onPressed: () async {
await _saveKeys(); await _saveKeys();
}, },
child: const Text('Do this later'), child: Text(S.of(context).doThisLater),
), ),
); );
childrens.add(const SizedBox(height: 10)); childrens.add(const SizedBox(height: 10));
@ -223,7 +224,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
onTap: () async { onTap: () async {
await _shareRecoveryKey(recoveryKey); await _shareRecoveryKey(recoveryKey);
}, },
text: 'Save key', text: S.of(context).saveKey,
), ),
); );
if (_hasTriedToSave) { if (_hasTriedToSave) {

View file

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/ui/account/password_entry_page.dart'; import 'package:photos/ui/account/password_entry_page.dart';
import 'package:photos/ui/common/dynamic_fab.dart'; import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/dialog_util.dart';
@ -43,15 +44,16 @@ class _RecoveryPageState extends State<RecoveryPage> {
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _recoveryKey.text.isNotEmpty, isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover', buttonText: S.of(context).recoverButton,
onPressedFunction: () async { onPressedFunction: () async {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
final dialog = createProgressDialog(context, "Decrypting..."); final dialog =
createProgressDialog(context, S.of(context).decrypting);
await dialog.show(); await dialog.show();
try { try {
await Configuration.instance.recover(_recoveryKey.text.trim()); await Configuration.instance.recover(_recoveryKey.text.trim());
await dialog.hide(); await dialog.hide();
showShortToast(context, "Recovery successful!"); showShortToast(context, S.of(context).recoverySuccessful);
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -66,11 +68,15 @@ class _RecoveryPageState extends State<RecoveryPage> {
); );
} catch (e) { } catch (e) {
await dialog.hide(); await dialog.hide();
String errMessage = "The recovery key you entered is incorrect"; String errMessage = S.of(context).incorrectRecoveryKeyBody;
if (e is AssertionError) { if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}'; errMessage = '$errMessage : ${e.message}';
} }
showErrorDialog(context, "Incorrect recovery key", errMessage); showErrorDialog(
context,
S.of(context).incorrectRecoveryKeyTitle,
errMessage,
);
} }
}, },
), ),
@ -85,7 +91,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
padding: padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20), const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text( child: Text(
'Forgot password', S.of(context).forgotPassword,
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
), ),
), ),
@ -94,7 +100,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
child: TextFormField( child: TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
hintText: "Enter your recovery key", hintText: S.of(context).enterYourRecoveryKey,
contentPadding: const EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder( border: UnderlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -128,15 +134,15 @@ class _RecoveryPageState extends State<RecoveryPage> {
onTap: () { onTap: () {
showErrorDialog( showErrorDialog(
context, context,
"Sorry", S.of(context).sorry,
"Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key", S.of(context).noRecoveryKeyNoDecryption,
); );
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Center( child: Center(
child: Text( child: Text(
"No recovery key?", S.of(context).noRecoveryKey,
style: style:
Theme.of(context).textTheme.subtitle1!.copyWith( Theme.of(context).textTheme.subtitle1!.copyWith(
fontSize: 14, fontSize: 14,

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/sessions.dart'; import 'package:photos/models/sessions.dart';
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/loading_widget.dart';
@ -33,7 +34,7 @@ class _SessionsPageState extends State<SessionsPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
title: const Text("Active sessions"), title: Text(S.of(context).activeSessions),
), ),
body: _getBody(), body: _getBody(),
); );
@ -111,7 +112,7 @@ class _SessionsPageState extends State<SessionsPage> {
} }
Future<void> _terminateSession(Session session) async { Future<void> _terminateSession(Session session) async {
final dialog = createProgressDialog(context, "Please wait..."); final dialog = createProgressDialog(context, S.of(context).pleaseWait);
await dialog.show(); await dialog.show();
try { try {
await UserService.instance.terminateSession(session.token); await UserService.instance.terminateSession(session.token);
@ -122,8 +123,8 @@ class _SessionsPageState extends State<SessionsPage> {
_logger.severe('failed to terminate'); _logger.severe('failed to terminate');
showErrorDialog( showErrorDialog(
context, context,
'Oops', S.of(context).oops,
"Something went wrong, please try again", S.of(context).somethingWentWrongPleaseTryAgain,
); );
} }
} }
@ -148,15 +149,15 @@ class _SessionsPageState extends State<SessionsPage> {
session.token == Configuration.instance.getToken(); session.token == Configuration.instance.getToken();
Widget text; Widget text;
if (isLoggingOutFromThisDevice) { if (isLoggingOutFromThisDevice) {
text = const Text( text = Text(
"This will log you out of this device!", S.of(context).thisWillLogYouOutOfThisDevice,
); );
} else { } else {
text = SingleChildScrollView( text = SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
const Text( Text(
"This will log you out of the following device:", S.of(context).thisWillLogYouOutOfTheFollowingDevice,
), ),
const Padding(padding: EdgeInsets.all(8)), const Padding(padding: EdgeInsets.all(8)),
Text( Text(
@ -168,13 +169,13 @@ class _SessionsPageState extends State<SessionsPage> {
); );
} }
final AlertDialog alert = AlertDialog( final AlertDialog alert = AlertDialog(
title: const Text("Terminate session?"), title: Text(S.of(context).terminateSession),
content: text, content: text,
actions: [ actions: [
TextButton( TextButton(
child: const Text( child: Text(
"Terminate", S.of(context).terminate,
style: TextStyle( style: const TextStyle(
color: Colors.red, color: Colors.red,
), ),
), ),
@ -189,7 +190,7 @@ class _SessionsPageState extends State<SessionsPage> {
), ),
TextButton( TextButton(
child: Text( child: Text(
"Cancel", S.of(context).cancel,
style: TextStyle( style: TextStyle(
color: isLoggingOutFromThisDevice color: isLoggingOutFromThisDevice
? Theme.of(context).colorScheme.greenAlternative ? Theme.of(context).colorScheme.greenAlternative
@ -214,7 +215,7 @@ class _SessionsPageState extends State<SessionsPage> {
Widget _getUAWidget(Session session) { Widget _getUAWidget(Session session) {
if (session.token == Configuration.instance.getToken()) { if (session.token == Configuration.instance.getToken()) {
return Text( return Text(
"This device", S.of(context).thisDevice,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.greenAlternative, color: Theme.of(context).colorScheme.greenAlternative,

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/lifecycle_event_handler.dart'; import 'package:photos/ui/lifecycle_event_handler.dart';
import 'package:pinput/pin_put/pin_put.dart'; import 'package:pinput/pin_put/pin_put.dart';
@ -51,8 +52,8 @@ class _TwoFactorAuthenticationPageState
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text( title: Text(
"Two-factor authentication", S.of(context).twofactorAuthenticationPageTitle,
), ),
), ),
body: _getBody(), body: _getBody(),
@ -65,9 +66,9 @@ class _TwoFactorAuthenticationPageState
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
const Text( Text(
"Enter the 6-digit code from\nyour authenticator app", S.of(context).enterThe6digitCodeFromnyourAuthenticatorApp,
style: TextStyle( style: const TextStyle(
height: 1.4, height: 1.4,
fontSize: 16, fontSize: 16,
), ),
@ -116,7 +117,7 @@ class _TwoFactorAuthenticationPageState
_verifyTwoFactorCode(_code); _verifyTwoFactorCode(_code);
} }
: null, : null,
child: const Text("Verify"), child: Text(S.of(context).verify),
), ),
), ),
const Padding(padding: EdgeInsets.all(30)), const Padding(padding: EdgeInsets.all(30)),
@ -127,10 +128,10 @@ class _TwoFactorAuthenticationPageState
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: const Center( child: Center(
child: Text( child: Text(
"Lost device?", S.of(context).lostDevice,
style: TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
fontSize: 12, fontSize: 12,
), ),

View file

@ -1,6 +1,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/dialog_util.dart';
@ -27,9 +28,9 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text( title: Text(
"Recover account", S.of(context).recoverAccount,
style: TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
), ),
), ),
@ -42,9 +43,9 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(60, 0, 60, 0), padding: const EdgeInsets.fromLTRB(60, 0, 60, 0),
child: TextFormField( child: TextFormField(
decoration: const InputDecoration( decoration: InputDecoration(
hintText: "Enter your recovery key", hintText: S.of(context).enterYourRecoveryKey,
contentPadding: EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
), ),
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
@ -77,7 +78,7 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
); );
} }
: null, : null,
child: const Text("Recover"), child: Text(S.of(context).recover),
), ),
), ),
GestureDetector( GestureDetector(
@ -85,15 +86,15 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
onTap: () { onTap: () {
showErrorDialog( showErrorDialog(
context, context,
"Contact support", S.of(context).contactSupport,
"Please drop an email to support@ente.io from your registered email address", S.of(context).dropSupportEmail("support@ente.io"),
); );
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(40), padding: const EdgeInsets.all(40),
child: Center( child: Center(
child: Text( child: Text(
"No recovery key?", S.of(context).noRecoveryKey,
style: TextStyle( style: TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
fontSize: 12, fontSize: 12,

View file

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
import 'package:photos/ui/account/recovery_key_page.dart'; import 'package:photos/ui/account/recovery_key_page.dart';
import 'package:photos/ui/lifecycle_event_handler.dart'; import 'package:photos/ui/lifecycle_event_handler.dart';
@ -75,8 +76,8 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
title: const Text( title: Text(
"Two-factor setup", S.of(context).twofactorSetup,
), ),
), ),
body: _getBody(), body: _getBody(),
@ -97,12 +98,12 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
TabBar( TabBar(
labelColor: Theme.of(context).colorScheme.greenAlternative, labelColor: Theme.of(context).colorScheme.greenAlternative,
unselectedLabelColor: Colors.grey, unselectedLabelColor: Colors.grey,
tabs: const [ tabs: [
Tab( Tab(
text: "Enter code", text: S.of(context).enterCode,
), ),
Tab( Tab(
text: "Scan code", text: S.of(context).scanCode,
) )
], ],
controller: _tabController, controller: _tabController,
@ -137,15 +138,15 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
await Clipboard.setData(ClipboardData(text: widget.secretCode)); await Clipboard.setData(ClipboardData(text: widget.secretCode));
showShortToast(context, "Code copied to clipboard"); showShortToast(context, S.of(context).codeCopiedToClipboard);
}, },
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Padding(padding: EdgeInsets.all(12)), const Padding(padding: EdgeInsets.all(12)),
const Text( Text(
"Copy-paste this code\nto your authenticator app", S.of(context).copypasteThisCodentoYourAuthenticatorApp,
style: TextStyle( style: const TextStyle(
height: 1.4, height: 1.4,
fontSize: 16, fontSize: 16,
), ),
@ -171,7 +172,7 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
), ),
const Padding(padding: EdgeInsets.all(6)), const Padding(padding: EdgeInsets.all(6)),
Text( Text(
"tap to copy", S.of(context).tapToCopy,
style: TextStyle(color: textColor.withOpacity(0.5)), style: TextStyle(color: textColor.withOpacity(0.5)),
) )
], ],
@ -184,9 +185,9 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
child: Column( child: Column(
children: [ children: [
const Padding(padding: EdgeInsets.all(12)), const Padding(padding: EdgeInsets.all(12)),
const Text( Text(
"Scan this barcode with\nyour authenticator app", S.of(context).scanThisBarcodeWithnyourAuthenticatorApp,
style: TextStyle( style: const TextStyle(
height: 1.4, height: 1.4,
fontSize: 16, fontSize: 16,
), ),
@ -207,9 +208,9 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
return Column( return Column(
children: [ children: [
const Padding(padding: EdgeInsets.all(12)), const Padding(padding: EdgeInsets.all(12)),
const Text( Text(
"Enter the 6-digit code from\nyour authenticator app", S.of(context).enterThe6digitCodeFromnyourAuthenticatorApp,
style: TextStyle( style: const TextStyle(
height: 1.4, height: 1.4,
fontSize: 16, fontSize: 16,
), ),
@ -253,7 +254,7 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
_enableTwoFactor(_code); _enableTwoFactor(_code);
} }
: null, : null,
child: const Text("Confirm"), child: Text(S.of(context).confirm),
), ),
const Padding(padding: EdgeInsets.only(bottom: 24)), const Padding(padding: EdgeInsets.only(bottom: 24)),
], ],
@ -275,13 +276,12 @@ class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
context, context,
RecoveryKeyPage( RecoveryKeyPage(
recoveryKey, recoveryKey,
"OK", S.of(context).ok,
showAppBar: true, showAppBar: true,
onDone: () {}, onDone: () {},
title: "Setup complete", title: S.of(context).setupComplete,
text: "Save your recovery key if you haven't already", text: S.of(context).saveYourRecoveryKeyIfYouHaventAlready,
subText: subText: S.of(context).thisCanBeUsedToRecoverYourAccountIfYou,
"This can be used to recover your account if you lose your second factor",
), ),
); );
} }

View file

@ -7,6 +7,7 @@ import 'package:logging/logging.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import 'package:photos/events/notification_event.dart'; import 'package:photos/events/notification_event.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/local_authentication_service.dart'; import 'package:photos/services/local_authentication_service.dart';
import 'package:photos/services/user_remote_flag_service.dart'; import 'package:photos/services/user_remote_flag_service.dart';
import 'package:photos/services/user_service.dart'; import 'package:photos/services/user_service.dart';
@ -29,7 +30,8 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
final Logger _logger = Logger((_VerifyRecoveryPageState).toString()); final Logger _logger = Logger((_VerifyRecoveryPageState).toString());
void _verifyRecoveryKey() async { void _verifyRecoveryKey() async {
final dialog = createProgressDialog(context, "Verifying recovery key..."); final dialog =
createProgressDialog(context, S.of(context).verifyingRecoveryKey);
await dialog.show(); await dialog.show();
try { try {
final String inputKey = _recoveryKey.text.trim(); final String inputKey = _recoveryKey.text.trim();
@ -58,10 +60,8 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
// todo: change this as per figma once the component is ready // todo: change this as per figma once the component is ready
await showErrorDialog( await showErrorDialog(
context, context,
"Recovery key verified", S.of(context).recoveryKeyVerified,
"Great! Your recovery key is valid. Thank you for verifying.\n" S.of(context).recoveryKeySuccessBody,
"\nPlease"
" remember to keep your recovery key safely backed up.",
); );
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {
@ -70,16 +70,13 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
} catch (e, s) { } catch (e, s) {
_logger.severe("failed to verify recovery key", e, s); _logger.severe("failed to verify recovery key", e, s);
await dialog.hide(); await dialog.hide();
const String errMessage = final String errMessage = S.of(context).invalidRecoveryKey;
"The recovery key you entered is not valid. Please make sure it "
"contains 24 words, and check the spelling of each.\n\nIf you "
"entered an older recovery code, make sure it is 64 characters long, and check each of them.";
final result = await showChoiceDialog( final result = await showChoiceDialog(
context, context,
title: "Invalid key", title: S.of(context).invalidKey,
body: errMessage, body: errMessage,
firstButtonLabel: "Try again", firstButtonLabel: S.of(context).tryAgain,
secondButtonLabel: "View recovery key", secondButtonLabel: S.of(context).viewRecoveryKey,
secondButtonAction: ButtonAction.second, secondButtonAction: ButtonAction.second,
); );
if (result!.action == ButtonAction.second) { if (result!.action == ButtonAction.second) {
@ -104,7 +101,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
context, context,
RecoveryKeyPage( RecoveryKeyPage(
recoveryKey, recoveryKey,
"OK", S.of(context).ok,
showAppBar: true, showAppBar: true,
onDone: () { onDone: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -149,14 +146,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: Text( child: Text(
'Confirm recovery key', S.of(context).confirmRecoveryKey,
style: enteTheme.textTheme.h3Bold, style: enteTheme.textTheme.h3Bold,
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
Text( Text(
"Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly.", S.of(context).recoveryKeyVerifyReason,
style: enteTheme.textTheme.small style: enteTheme.textTheme.small
.copyWith(color: enteTheme.colorScheme.textMuted), .copyWith(color: enteTheme.colorScheme.textMuted),
), ),
@ -164,7 +161,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
TextFormField( TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
hintText: "Enter your recovery key", hintText: S.of(context).enterYourRecoveryKey,
contentPadding: const EdgeInsets.all(20), contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder( border: UnderlineInputBorder(
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -197,7 +194,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
children: [ children: [
GradientButton( GradientButton(
onTap: _verifyRecoveryKey, onTap: _verifyRecoveryKey,
text: "Confirm", text: S.of(context).confirm,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
], ],

View file

@ -6,6 +6,7 @@ import 'package:photos/core/event_bus.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import 'package:photos/events/notification_event.dart'; import 'package:photos/events/notification_event.dart';
import 'package:photos/events/sync_status_update_event.dart'; import 'package:photos/events/sync_status_update_event.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/sync_service.dart'; import 'package:photos/services/sync_service.dart';
import 'package:photos/services/user_remote_flag_service.dart'; import 'package:photos/services/user_remote_flag_service.dart';
import 'package:photos/theme/text_style.dart'; import 'package:photos/theme/text_style.dart';
@ -100,14 +101,14 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
_showErrorBanner _showErrorBanner
? HeaderErrorWidget(error: _syncError) ? HeaderErrorWidget(error: _syncError)
: const SizedBox.shrink(), : const SizedBox.shrink(),
UserRemoteFlagService.instance.shouldShowRecoveryVerification() !UserRemoteFlagService.instance.shouldShowRecoveryVerification()
? Padding( ? Padding(
padding: padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12), const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
child: NotificationWidget( child: NotificationWidget(
startIcon: Icons.error_outline, startIcon: Icons.error_outline,
actionIcon: Icons.arrow_forward, actionIcon: Icons.arrow_forward,
text: "Confirm your recovery key", text: S.of(context).confirmYourRecoveryKey,
onTap: () async => { onTap: () async => {
await routeToPage( await routeToPage(
context, context,

View file

@ -1,6 +1,7 @@
import 'package:email_validator/email_validator.dart'; import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/collection.dart'; import 'package:photos/models/collection.dart';
import 'package:photos/services/collections_service.dart'; import 'package:photos/services/collections_service.dart';
import 'package:photos/theme/ente_theme.dart'; import 'package:photos/theme/ente_theme.dart';
@ -60,7 +61,9 @@ class _AddParticipantPage extends State<AddParticipantPage> {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: isKeypadOpen, resizeToAvoidBottomInset: isKeypadOpen,
appBar: AppBar( appBar: AppBar(
title: Text(widget.isAddingViewer ? "Add viewer" : "Add collaborator"), title: Text(widget.isAddingViewer
? S.of(context).addViewer
: S.of(context).addCollaborator),
), ),
body: Column( body: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -70,7 +73,7 @@ class _AddParticipantPage extends State<AddParticipantPage> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text( child: Text(
"Add a new email", S.of(context).addANewEmail,
style: enteTextTheme.small style: enteTextTheme.small
.copyWith(color: enteColorScheme.textMuted), .copyWith(color: enteColorScheme.textMuted),
), ),
@ -88,21 +91,22 @@ class _AddParticipantPage extends State<AddParticipantPage> {
child: Column( child: Column(
children: [ children: [
!isEmailListEmpty !isEmailListEmpty
? const MenuSectionTitle( ? MenuSectionTitle(
title: "Or pick an existing one", title: S.of(context).orPickAnExistingOne,
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= suggestedUsers.length) { if (index >= suggestedUsers.length) {
return const Padding( return Padding(
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8.0, vertical: 8.0,
), ),
child: MenuSectionDescriptionWidget( child: MenuSectionDescriptionWidget(
content: content: S
"Collaborators can add photos and videos to the shared album.", .of(context)
.collaboratorsCanAddPhotosAndVideosToTheSharedAlbum,
), ),
); );
} }
@ -176,8 +180,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
buttonType: ButtonType.primary, buttonType: ButtonType.primary,
buttonSize: ButtonSize.large, buttonSize: ButtonSize.large,
labelText: widget.isAddingViewer labelText: widget.isAddingViewer
? "Add viewer" ? S.of(context).addViewer
: "Add collaborator", : S.of(context).addCollaborator,
isDisabled: (selectedEmail == '' && !_emailIsValid), isDisabled: (selectedEmail == '' && !_emailIsValid),
onTap: (selectedEmail == '' && !_emailIsValid) onTap: (selectedEmail == '' && !_emailIsValid)
? null ? null
@ -204,8 +208,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
if ((selectedEmail == '' && !_emailIsValid)) { if ((selectedEmail == '' && !_emailIsValid)) {
await showErrorDialog( await showErrorDialog(
context, context,
"Invalid email address", S.of(context).invalidEmailAddress,
"Please enter a valid email address.", S.of(context).enterValidEmail,
); );
return; return;
} }
@ -261,7 +265,7 @@ class _AddParticipantPage extends State<AddParticipantPage> {
), ),
fillColor: getEnteColorScheme(context).fillFaint, fillColor: getEnteColorScheme(context).fillFaint,
filled: true, filled: true,
hintText: 'Enter email', hintText: S.of(context).enterEmail,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 14, vertical: 14,

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/extensions/list.dart'; import 'package:photos/extensions/list.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/collection.dart'; import 'package:photos/models/collection.dart';
import 'package:photos/theme/ente_theme.dart'; import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart'; import 'package:photos/ui/components/captioned_text_widget.dart';
@ -95,14 +96,14 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
children: [ children: [
Column( Column(
children: [ children: [
const MenuSectionTitle( MenuSectionTitle(
title: "Owner", title: S.of(context).albumOwner,
iconData: Icons.admin_panel_settings_outlined, iconData: Icons.admin_panel_settings_outlined,
), ),
MenuItemWidget( MenuItemWidget(
captionedTextWidget: CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: isOwner title: isOwner
? "You" ? S.of(context).you
: widget.collection.owner?.email ?? '', : widget.collection.owner?.email ?? '',
makeTextBold: isOwner, makeTextBold: isOwner,
), ),
@ -130,8 +131,8 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == 0 && (isOwner || collaborators.isNotEmpty)) { if (index == 0 && (isOwner || collaborators.isNotEmpty)) {
return const MenuSectionTitle( return MenuSectionTitle(
title: "Collaborator", title: S.of(context).collaborator,
iconData: Icons.edit_outlined, iconData: Icons.edit_outlined,
); );
} else if (index > 0 && index <= collaborators.length) { } else if (index > 0 && index <= collaborators.length) {
@ -146,7 +147,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
MenuItemWidget( MenuItemWidget(
captionedTextWidget: CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: isSameAsLoggedInUser title: isSameAsLoggedInUser
? "You" ? S.of(context).you
: currentUser.email, : currentUser.email,
makeTextBold: isSameAsLoggedInUser, makeTextBold: isSameAsLoggedInUser,
), ),
@ -182,8 +183,8 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
return MenuItemWidget( return MenuItemWidget(
captionedTextWidget: CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: collaborators.isNotEmpty title: collaborators.isNotEmpty
? "Add more" ? S.of(context).addMore
: "Add collaborator", : S.of(context).addCollaborator,
makeTextBold: true, makeTextBold: true,
), ),
leadingIcon: Icons.add_outlined, leadingIcon: Icons.add_outlined,
@ -207,8 +208,8 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == 0 && (isOwner || viewers.isNotEmpty)) { if (index == 0 && (isOwner || viewers.isNotEmpty)) {
return const MenuSectionTitle( return MenuSectionTitle(
title: "Viewer", title: S.of(context).viewer,
iconData: Icons.photo_outlined, iconData: Icons.photo_outlined,
); );
} else if (index > 0 && index <= viewers.length) { } else if (index > 0 && index <= viewers.length) {
@ -222,7 +223,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
MenuItemWidget( MenuItemWidget(
captionedTextWidget: CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: isSameAsLoggedInUser title: isSameAsLoggedInUser
? "You" ? S.of(context).you
: currentUser.email, : currentUser.email,
makeTextBold: isSameAsLoggedInUser, makeTextBold: isSameAsLoggedInUser,
), ),
@ -257,7 +258,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
} else if (index == (1 + viewers.length) && isOwner) { } else if (index == (1 + viewers.length) && isOwner) {
return MenuItemWidget( return MenuItemWidget(
captionedTextWidget: CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: viewers.isNotEmpty ? "Add more" : "Add viewer", title: viewers.isNotEmpty
? S.of(context).addMore
: S.of(context).addViewer,
makeTextBold: true, makeTextBold: true,
), ),
leadingIcon: Icons.add_outlined, leadingIcon: Icons.add_outlined,

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/collection.dart'; import 'package:photos/models/collection.dart';
import 'package:photos/services/collections_service.dart'; import 'package:photos/services/collections_service.dart';
import 'package:photos/theme/colors.dart'; import 'package:photos/theme/colors.dart';
@ -52,8 +53,8 @@ class _ManageIndividualParticipantState
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
const TitleBarTitleWidget( TitleBarTitleWidget(
title: "Manage", title: S.of(context).manage,
), ),
Text( Text(
widget.user.email.toString().trim(), widget.user.email.toString().trim(),
@ -65,10 +66,10 @@ class _ManageIndividualParticipantState
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const MenuSectionTitle(title: "Added as"), MenuSectionTitle(title: S.of(context).addedAs),
MenuItemWidget( MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: "Collaborator", title: S.of(context).collaborator,
), ),
leadingIcon: Icons.edit_outlined, leadingIcon: Icons.edit_outlined,
menuItemColor: getEnteColorScheme(context).fillFaint, menuItemColor: getEnteColorScheme(context).fillFaint,
@ -97,8 +98,8 @@ class _ManageIndividualParticipantState
bgColor: getEnteColorScheme(context).fillFaint, bgColor: getEnteColorScheme(context).fillFaint,
), ),
MenuItemWidget( MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: "Viewer", title: S.of(context).viewer,
), ),
leadingIcon: Icons.photo_outlined, leadingIcon: Icons.photo_outlined,
leadingIconColor: getEnteColorScheme(context).strokeBase, leadingIconColor: getEnteColorScheme(context).strokeBase,
@ -110,10 +111,12 @@ class _ManageIndividualParticipantState
: () async { : () async {
final actionResult = await showChoiceActionSheet( final actionResult = await showChoiceActionSheet(
context, context,
title: "Change permissions?", title: S.of(context).changePermissions,
firstButtonLabel: "Yes, convert to viewer", firstButtonLabel: S.of(context).yesConvertToViewer,
body: body: S
'${widget.user.email} will not be able to add more photos to this album\n\nThey will still be able to remove existing photos added by them', .of(context)
.cannotAddMorePhotosAfterBecomingViewer(
widget.user.email),
isCritical: true, isCritical: true,
); );
if (actionResult?.action != null) { if (actionResult?.action != null) {
@ -141,15 +144,16 @@ class _ManageIndividualParticipantState
}, },
isTopBorderRadiusRemoved: true, isTopBorderRadiusRemoved: true,
), ),
const MenuSectionDescriptionWidget( MenuSectionDescriptionWidget(
content: content: S
"Collaborators can add photos and videos to the shared album.", .of(context)
.collaboratorsCanAddPhotosAndVideosToTheSharedAlbum,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const MenuSectionTitle(title: "Remove participant"), MenuSectionTitle(title: S.of(context).removeParticipant),
MenuItemWidget( MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget( captionedTextWidget: CaptionedTextWidget(
title: "Remove", title: S.of(context).remove,
textColor: warning500, textColor: warning500,
makeTextBold: true, makeTextBold: true,
), ),