Integrate email notifications into signup and login (#38)
This commit is contained in:
1 parent
326d58f705
commit
ad5a061164
55 files changed
+1859
-3464
No files matched your search
@@ -46,3 +46,6 @@ app.*.map.json
|
||||
|
||||
# Widget Preview related
|
||||
.widget_preview/
|
||||
|
||||
# Localization bindings are regenerated from ARB sources.
|
||||
/lib/generated/i18n/
|
||||
@@ -32,7 +32,7 @@ From the repository root, `make test-frontend` runs both theme suites and the ap
|
||||
|
||||
## English and Russian
|
||||
|
||||
Localization follows Sendico's Flutter pattern: `lib/l10n/en.arb` is the template, `ru.arb` contains Russian translations, and `flutter gen-l10n` writes `lib/generated/i18n/`. Edit ARB files, not generated Dart files. Flutter's Material and Cupertino localizations are included through the generated delegates.
|
||||
Localization follows Sendico's Flutter pattern: `lib/l10n/en.arb` is the template, `ru.arb` contains Russian translations, and `flutter gen-l10n` writes `lib/generated/i18n/`. Generated Dart files are ignored and untracked. Run `flutter gen-l10n` after a clean checkout and after editing ARB files; `make test-frontend` runs it before tests. Edit ARB files, not generated Dart files. Flutter's Material and Cupertino localizations are included through the generated delegates.
|
||||
|
||||
The language dropdown is available in both authentication modes. Startup reads `sxchnge_last_locale` using shared preferences; without a saved choice it uses the device language. Russian regional variants resolve to Russian and unsupported languages fall back to English. Preference writes are serialized, and storage failures leave the selected language usable for the current session with a localized notice. Only the language preference is stored; credentials remain transient.
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class AuthFeedback extends StatelessWidget {
|
||||
? (signUp ? l.signupPreview : l.loginPreview)
|
||||
: l.authCompleted)
|
||||
: switch (error) {
|
||||
AuthError.accountNotVerified => l.authVerifyEmail,
|
||||
AuthError.invalidCredentials => l.authInvalidCredentials,
|
||||
AuthError.emailInUse => l.authEmailInUse,
|
||||
AuthError.unavailable => l.authUnavailable,
|
||||
|
||||
@@ -104,6 +104,11 @@ class _AuthFormState extends State<AuthForm> {
|
||||
locale: locale,
|
||||
);
|
||||
if (!mounted || _signUp) return;
|
||||
if (login.state.error == AuthError.accountNotVerified) {
|
||||
_password.clear();
|
||||
context.goNamed(AppRoutes.verificationSent, extra: _email.text.trim());
|
||||
return;
|
||||
}
|
||||
final challenge = login.state.result?.challenge;
|
||||
if (challenge != null) {
|
||||
_password.clear();
|
||||
@@ -329,10 +334,11 @@ class _AuthFormState extends State<AuthForm> {
|
||||
AuthFeedback(state: state, signUp: _signUp),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
TextButton(
|
||||
onPressed: () => context.go('/accounts'),
|
||||
child: Text(l.wEnter),
|
||||
),
|
||||
if (context.read<TwoFactorService>().isSimulated)
|
||||
TextButton(
|
||||
onPressed: () => context.go('/accounts'),
|
||||
child: Text(l.wEnter),
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,860 +0,0 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'SXCHNGE · Your next move';
|
||||
|
||||
@override
|
||||
String get nextChapter => 'YOUR NEXT CHAPTER';
|
||||
|
||||
@override
|
||||
String get loginHeading => 'Hey, welcome\nback.';
|
||||
|
||||
@override
|
||||
String get signupHeading => 'Make your\nfirst move.';
|
||||
|
||||
@override
|
||||
String get loginIntro => 'Your next move is just a login away.';
|
||||
|
||||
@override
|
||||
String get signupIntro => 'A new account. A world of possibility.';
|
||||
|
||||
@override
|
||||
String get login => 'Log in';
|
||||
|
||||
@override
|
||||
String get signup => 'Sign up';
|
||||
|
||||
@override
|
||||
String get name => 'Your name';
|
||||
|
||||
@override
|
||||
String get email => 'Email address';
|
||||
|
||||
@override
|
||||
String get emailHint => 'you@example.com';
|
||||
|
||||
@override
|
||||
String get password => 'Password';
|
||||
|
||||
@override
|
||||
String get confirmPassword => 'Confirm password';
|
||||
|
||||
@override
|
||||
String get passwordHint => 'Use at least 12 characters.';
|
||||
|
||||
@override
|
||||
String get showPassword => 'Show password';
|
||||
|
||||
@override
|
||||
String get hidePassword => 'Hide password';
|
||||
|
||||
@override
|
||||
String get showConfirmation => 'Show confirmation';
|
||||
|
||||
@override
|
||||
String get hideConfirmation => 'Hide confirmation';
|
||||
|
||||
@override
|
||||
String get createAccount => 'Create account';
|
||||
|
||||
@override
|
||||
String get letsGo => 'Let’s go';
|
||||
|
||||
@override
|
||||
String get loginPreview => 'You’re signed in.';
|
||||
|
||||
@override
|
||||
String get signupPreview => 'Your account is ready.';
|
||||
|
||||
@override
|
||||
String get alreadyMember => 'Already part of sxchnge?';
|
||||
|
||||
@override
|
||||
String get newHere => 'New around here?';
|
||||
|
||||
@override
|
||||
String get loginInstead => 'Log in instead';
|
||||
|
||||
@override
|
||||
String get createAnAccount => 'Create an account';
|
||||
|
||||
@override
|
||||
String get brandEyebrow => 'A LITTLE CHANGE. A BIG NEXT STEP.';
|
||||
|
||||
@override
|
||||
String get brandHeading => 'Good moves.\nStart here.';
|
||||
|
||||
@override
|
||||
String get brandIntro => 'A fresh way forward.\nMake it yours with sxchnge.';
|
||||
|
||||
@override
|
||||
String get brandCard => 'Less friction.\nMore possibility.';
|
||||
|
||||
@override
|
||||
String get brandFooter => 'YOUR NEXT MOVE STARTS WITH YOU.';
|
||||
|
||||
@override
|
||||
String get emailRequired => 'Enter your email address.';
|
||||
|
||||
@override
|
||||
String get emailInvalid => 'Enter a valid email address.';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Enter your name.';
|
||||
|
||||
@override
|
||||
String get passwordRequired => 'Enter your password.';
|
||||
|
||||
@override
|
||||
String get confirmationRequired => 'Confirm your password.';
|
||||
|
||||
@override
|
||||
String get passwordMismatch => 'Passwords do not match.';
|
||||
|
||||
@override
|
||||
String get language => 'Language';
|
||||
|
||||
@override
|
||||
String get preferenceNotSaved =>
|
||||
'Language changed for this session. Your preference could not be saved.';
|
||||
|
||||
@override
|
||||
String get authWorking => 'Please wait…';
|
||||
|
||||
@override
|
||||
String get authCompleted => 'Request completed.';
|
||||
|
||||
@override
|
||||
String get authInvalidCredentials =>
|
||||
'Email or password is incorrect. Please try again.';
|
||||
|
||||
@override
|
||||
String get authEmailInUse =>
|
||||
'This email is already registered. Try logging in.';
|
||||
|
||||
@override
|
||||
String get authUnavailable => 'The service is unavailable. Please try again.';
|
||||
|
||||
@override
|
||||
String get authUnexpected => 'Something went wrong. Please try again.';
|
||||
|
||||
@override
|
||||
String get checkInbox => 'Check your inbox.';
|
||||
|
||||
@override
|
||||
String get verificationSent =>
|
||||
'We’ve sent you a verification email. Open the link in your inbox to confirm your email address.';
|
||||
|
||||
@override
|
||||
String get verificationSending => 'Preparing your verification email…';
|
||||
|
||||
@override
|
||||
String get verificationChecking => 'Verifying your email…';
|
||||
|
||||
@override
|
||||
String get verificationVerified => 'Your email\nis verified.';
|
||||
|
||||
@override
|
||||
String get verificationVerifiedBody =>
|
||||
'One step done. You can now return to login. Identity verification is a separate step.';
|
||||
|
||||
@override
|
||||
String get verificationExpired => 'This link has expired.';
|
||||
|
||||
@override
|
||||
String get verificationExpiredBody =>
|
||||
'Request a new verification email and use the latest link.';
|
||||
|
||||
@override
|
||||
String get verificationInvalid => 'This link isn’t valid.';
|
||||
|
||||
@override
|
||||
String get verificationInvalidBody =>
|
||||
'The confirmation link is missing or invalid. Open the complete link from your email, or request a new one.';
|
||||
|
||||
@override
|
||||
String get verificationInternal => 'Something went wrong.';
|
||||
|
||||
@override
|
||||
String get verificationInternalBody =>
|
||||
'We couldn’t complete your request because of an internal error. Please try again.';
|
||||
|
||||
@override
|
||||
String get verificationResend => 'Resend verification email';
|
||||
|
||||
@override
|
||||
String get verificationRequest => 'Send a new verification email';
|
||||
|
||||
@override
|
||||
String get verificationRetry => 'Retry verification';
|
||||
|
||||
@override
|
||||
String get verificationBack => 'Back to login';
|
||||
|
||||
@override
|
||||
String get verificationHint =>
|
||||
'No email? Check your spam folder or request another below.';
|
||||
|
||||
@override
|
||||
String get verificationDemoLink => 'Continue to confirmation';
|
||||
|
||||
@override
|
||||
String get verificationStep => 'CONFIRM YOUR EMAIL';
|
||||
|
||||
@override
|
||||
String get verificationRecipient => 'Recipient';
|
||||
|
||||
@override
|
||||
String get verificationChangeEmail => 'Change email';
|
||||
|
||||
@override
|
||||
String get kycTitle => 'Verification & limits';
|
||||
|
||||
@override
|
||||
String get kycIntro =>
|
||||
'Your account, at your pace. Verify your identity when you need higher limits.';
|
||||
|
||||
@override
|
||||
String get kycUnverified => 'Not verified';
|
||||
|
||||
@override
|
||||
String get kycPending => 'In review';
|
||||
|
||||
@override
|
||||
String get kycApproved => 'Identity verified';
|
||||
|
||||
@override
|
||||
String get kycChanges => 'New photos needed';
|
||||
|
||||
@override
|
||||
String get kycDenied => 'Verification declined';
|
||||
|
||||
@override
|
||||
String get kycUnverifiedBody =>
|
||||
'You can keep using your account with basic limits. Submit a passport photo and a selfie holding it to request an upgrade.';
|
||||
|
||||
@override
|
||||
String get kycPendingBody =>
|
||||
'Your submission is awaiting a human review. Basic limits remain active until approval. You do not need to submit again.';
|
||||
|
||||
@override
|
||||
String get kycApprovedBody =>
|
||||
'Your review is complete. Check your current limits below.';
|
||||
|
||||
@override
|
||||
String get kycChangesBody =>
|
||||
'Your account remains available with basic limits. Replace the photos and submit a new review request.';
|
||||
|
||||
@override
|
||||
String get kycDeniedBody =>
|
||||
'Basic limits remain available. If you believe this decision is incorrect, contact support for a review.';
|
||||
|
||||
@override
|
||||
String get kycPassportReason =>
|
||||
'The passport details could not be read. Retake the photo in good light, with every edge visible.';
|
||||
|
||||
@override
|
||||
String get kycSelfieReason =>
|
||||
'Your face or passport was not clear in the selfie. Keep both visible and in focus.';
|
||||
|
||||
@override
|
||||
String get kycExpiredReason =>
|
||||
'The passport has expired. Use a valid passport and take both photos again.';
|
||||
|
||||
@override
|
||||
String get kycDeniedReason =>
|
||||
'We could not verify your identity from this submission.';
|
||||
|
||||
@override
|
||||
String get kycStart => 'Verify identity';
|
||||
|
||||
@override
|
||||
String get kycResubmit => 'Replace photos';
|
||||
|
||||
@override
|
||||
String get kycRefresh => 'Refresh status & limits';
|
||||
|
||||
@override
|
||||
String get kycSupport => 'Contact support';
|
||||
|
||||
@override
|
||||
String get kycSupportBody =>
|
||||
'Support is currently unavailable. Please try again later.';
|
||||
|
||||
@override
|
||||
String get kycBasic => 'Basic';
|
||||
|
||||
@override
|
||||
String get kycVerifiedTier => 'Verified';
|
||||
|
||||
@override
|
||||
String get kycLimits => 'Your limits';
|
||||
|
||||
@override
|
||||
String get kycDay => 'Daily volume';
|
||||
|
||||
@override
|
||||
String get kycMonth => 'Monthly volume';
|
||||
|
||||
@override
|
||||
String get kycCurrent => 'Current cap';
|
||||
|
||||
@override
|
||||
String get kycRemaining => 'Available';
|
||||
|
||||
@override
|
||||
String get kycAfter => 'After approval';
|
||||
|
||||
@override
|
||||
String get kycLimitsError =>
|
||||
'Limits could not be refreshed. Any values below are out of date. Retry to see your current access.';
|
||||
|
||||
@override
|
||||
String get kycLimitsEmpty =>
|
||||
'No limits were returned. Refresh or check with support before making a transaction.';
|
||||
|
||||
@override
|
||||
String get kycLoading => 'Loading your status…';
|
||||
|
||||
@override
|
||||
String get kycPassport => 'Passport photo';
|
||||
|
||||
@override
|
||||
String get kycSelfie => 'Selfie with passport';
|
||||
|
||||
@override
|
||||
String get kycReview => 'Review your photos';
|
||||
|
||||
@override
|
||||
String get kycPassportHelp =>
|
||||
'Open your valid passport to the photo page. Show all four edges and keep text sharp. Avoid glare, blur and covered details.';
|
||||
|
||||
@override
|
||||
String get kycSelfieHelp =>
|
||||
'Hold the open passport beside your face. Show your whole face and the photo page in the same image. Keep both sharp and uncovered.';
|
||||
|
||||
@override
|
||||
String get kycFileRules =>
|
||||
'JPEG or PNG · up to 10 MB · at least 600 × 600 pixels. Take a photo with your camera, then choose it here.';
|
||||
|
||||
@override
|
||||
String get kycChoose => 'Choose photo';
|
||||
|
||||
@override
|
||||
String get kycReplace => 'Choose a different photo';
|
||||
|
||||
@override
|
||||
String get kycRemove => 'Remove photo';
|
||||
|
||||
@override
|
||||
String get kycNext => 'Continue';
|
||||
|
||||
@override
|
||||
String get kycBack => 'Back';
|
||||
|
||||
@override
|
||||
String get kycSkip => 'Keep basic limits';
|
||||
|
||||
@override
|
||||
String get kycClose => 'Back to account';
|
||||
|
||||
@override
|
||||
String get kycReviewHelp =>
|
||||
'Check that both photos are readable and show the same passport. A staff member will review them; submission does not immediately raise your limits.';
|
||||
|
||||
@override
|
||||
String get kycConfirm => 'These photos show me and my own valid passport.';
|
||||
|
||||
@override
|
||||
String get kycSubmit => 'Submit for review';
|
||||
|
||||
@override
|
||||
String get kycWorking => 'Processing… Please keep this page open.';
|
||||
|
||||
@override
|
||||
String get kycDiscardTitle => 'Discard these photos?';
|
||||
|
||||
@override
|
||||
String get kycDiscardBody =>
|
||||
'Your draft is only in memory. Leaving will remove the selected photos; you can start again later.';
|
||||
|
||||
@override
|
||||
String get kycStay => 'Keep editing';
|
||||
|
||||
@override
|
||||
String get kycDiscard => 'Discard & leave';
|
||||
|
||||
@override
|
||||
String get kycErrorUnavailable =>
|
||||
'The service is unavailable. Your selected photos are still here. Try again.';
|
||||
|
||||
@override
|
||||
String get kycErrorInternal =>
|
||||
'Something went wrong. Try again; your selected photos have been kept.';
|
||||
|
||||
@override
|
||||
String get kycErrorPermission =>
|
||||
'We could not open that photo. Check file access or choose another image.';
|
||||
|
||||
@override
|
||||
String get kycErrorFormat =>
|
||||
'Choose a JPEG or PNG image. Other formats are not supported.';
|
||||
|
||||
@override
|
||||
String get kycErrorLarge =>
|
||||
'This image is too large. Choose one under 10 MB and 40 megapixels.';
|
||||
|
||||
@override
|
||||
String get kycErrorUnreadable =>
|
||||
'This image is damaged or cannot be read. Choose another photo.';
|
||||
|
||||
@override
|
||||
String get kycErrorSmall =>
|
||||
'The photo is too small. Use an image at least 600 pixels wide and high.';
|
||||
|
||||
@override
|
||||
String get kycErrorIncomplete =>
|
||||
'Choose both photos and confirm they belong to you before submitting.';
|
||||
|
||||
@override
|
||||
String get kycErrorSession =>
|
||||
'Your session has expired. Return to login, then check your verification status.';
|
||||
|
||||
@override
|
||||
String get kycErrorRate =>
|
||||
'Too many requests. Wait a moment before trying again.';
|
||||
|
||||
@override
|
||||
String get kycErrorConflict =>
|
||||
'Your review status has changed. Refresh it before continuing.';
|
||||
|
||||
@override
|
||||
String get kycDemoControls => 'Review scenarios';
|
||||
|
||||
@override
|
||||
String get kycDemoApprove => 'Approve';
|
||||
|
||||
@override
|
||||
String get kycDemoCorrect => 'Request clearer passport';
|
||||
|
||||
@override
|
||||
String get kycDemoSelfie => 'Request clearer selfie';
|
||||
|
||||
@override
|
||||
String get kycDemoExpired => 'Passport expired';
|
||||
|
||||
@override
|
||||
String get kycDemoDeny => 'Decline';
|
||||
|
||||
@override
|
||||
String get kycPreview => 'Explore verification & limits';
|
||||
|
||||
@override
|
||||
String get kycChooseFirst => 'Choose a photo to continue.';
|
||||
|
||||
@override
|
||||
String get kycStatusStale =>
|
||||
'Status could not be refreshed. The status shown is the last known result.';
|
||||
|
||||
@override
|
||||
String get kycUsed => 'Used';
|
||||
|
||||
@override
|
||||
String get kycReserved => 'Reserved';
|
||||
|
||||
@override
|
||||
String get kycUsageHelp =>
|
||||
'Used is completed activity. Reserved is held for pending transactions. Both count toward your limit.';
|
||||
|
||||
@override
|
||||
String get kycOccupied => 'Used + reserved';
|
||||
|
||||
@override
|
||||
String get kycNoCapacity => 'No capacity available';
|
||||
|
||||
@override
|
||||
String get wAccounts => 'Accounts';
|
||||
|
||||
@override
|
||||
String get wActivity => 'Activity';
|
||||
|
||||
@override
|
||||
String get wLimits => 'Limits';
|
||||
|
||||
@override
|
||||
String get wSettings => 'Settings';
|
||||
|
||||
@override
|
||||
String get wHeadline => 'Your money.\nYour next move.';
|
||||
|
||||
@override
|
||||
String get wSubheading => 'Top up. Exchange. Go further.';
|
||||
|
||||
@override
|
||||
String get wAvailable => 'Available to you';
|
||||
|
||||
@override
|
||||
String get wTopUp => 'Top up';
|
||||
|
||||
@override
|
||||
String get wExchange => 'Exchange';
|
||||
|
||||
@override
|
||||
String get wPayout => 'Pay out';
|
||||
|
||||
@override
|
||||
String get wRecent => 'Recent activity';
|
||||
|
||||
@override
|
||||
String get wSeeAll => 'See all';
|
||||
|
||||
@override
|
||||
String get wEmptyActivity =>
|
||||
'Your next move starts here. Your transactions will appear below.';
|
||||
|
||||
@override
|
||||
String get wEmptyAccounts =>
|
||||
'Your accounts are not ready yet. Try refreshing.';
|
||||
|
||||
@override
|
||||
String get wRefresh => 'Refresh';
|
||||
|
||||
@override
|
||||
String get wSource => 'From account';
|
||||
|
||||
@override
|
||||
String get wDestination => 'To account';
|
||||
|
||||
@override
|
||||
String get wReceiveAccount => 'Receiving account';
|
||||
|
||||
@override
|
||||
String get wAmount => 'Amount';
|
||||
|
||||
@override
|
||||
String get wRecipient => 'Recipient';
|
||||
|
||||
@override
|
||||
String get wRecipientHint => 'Recipient name or reference';
|
||||
|
||||
@override
|
||||
String get wReview => 'Review';
|
||||
|
||||
@override
|
||||
String get wReviewTitle => 'One last look.';
|
||||
|
||||
@override
|
||||
String get wFee => 'Fee';
|
||||
|
||||
@override
|
||||
String get wTotalDebit => 'Leaves your account';
|
||||
|
||||
@override
|
||||
String get wCredit => 'Arrives in your account';
|
||||
|
||||
@override
|
||||
String get wRecipientGets => 'Recipient receives';
|
||||
|
||||
@override
|
||||
String get wYouPay => 'You pay';
|
||||
|
||||
@override
|
||||
String get wRate => 'Exchange rate';
|
||||
|
||||
@override
|
||||
String get wQuoteNotice =>
|
||||
'These terms are valid for 60 seconds. Confirm them before continuing.';
|
||||
|
||||
@override
|
||||
String get wExpired =>
|
||||
'This quote expired. Refresh it and review the new terms.';
|
||||
|
||||
@override
|
||||
String get wNewQuote => 'Refresh quote';
|
||||
|
||||
@override
|
||||
String get wConfirm => 'Confirm transaction';
|
||||
|
||||
@override
|
||||
String get wEdit => 'Edit details';
|
||||
|
||||
@override
|
||||
String get wClose => 'Close';
|
||||
|
||||
@override
|
||||
String get wWorking => 'Working on it…';
|
||||
|
||||
@override
|
||||
String get wPending => 'Processing';
|
||||
|
||||
@override
|
||||
String get wCompleted => 'Completed';
|
||||
|
||||
@override
|
||||
String get wFailed => 'Not completed';
|
||||
|
||||
@override
|
||||
String get wPendingTopUp =>
|
||||
'Your top-up is processing. Your available balance has not changed yet.';
|
||||
|
||||
@override
|
||||
String get wPendingExchange =>
|
||||
'The amount and fee have left your available balance. The exchanged amount will appear in the receiving account when completed.';
|
||||
|
||||
@override
|
||||
String get wPendingPayout =>
|
||||
'The amount and fee have left your available balance. The payout is still processing.';
|
||||
|
||||
@override
|
||||
String get wCompleteTopUp =>
|
||||
'Your account has been credited. The money is available to use.';
|
||||
|
||||
@override
|
||||
String get wCompleteExchange =>
|
||||
'Your exchange is complete. The money is available in the receiving account.';
|
||||
|
||||
@override
|
||||
String get wCompletePayout =>
|
||||
'Your payout is complete. The amount and fee have been deducted from your account.';
|
||||
|
||||
@override
|
||||
String get wFailedTopUp =>
|
||||
'The top-up did not complete. No money was added to your account.';
|
||||
|
||||
@override
|
||||
String get wFailedOutgoing =>
|
||||
'The transaction did not complete. The amount and fee are available in your account again.';
|
||||
|
||||
@override
|
||||
String get wUnavailable =>
|
||||
'Could not refresh. The displayed information may be out of date. Try again.';
|
||||
|
||||
@override
|
||||
String get wInvalidAmount =>
|
||||
'Enter an amount greater than zero, using the currency’s decimal precision.';
|
||||
|
||||
@override
|
||||
String get wInsufficient =>
|
||||
'Not enough available money for this amount and fee. Top up or enter a smaller amount.';
|
||||
|
||||
@override
|
||||
String get wLimitError =>
|
||||
'This amount exceeds your available limit. Check your limits before continuing.';
|
||||
|
||||
@override
|
||||
String get wRecipientError =>
|
||||
'Enter a sample recipient between 6 and 140 characters.';
|
||||
|
||||
@override
|
||||
String get wUnsupported =>
|
||||
'This combination is unavailable. Choose another account.';
|
||||
|
||||
@override
|
||||
String get wNotFound =>
|
||||
'This item could not be found. Return to your accounts.';
|
||||
|
||||
@override
|
||||
String get wUncertain =>
|
||||
'We could not confirm the outcome. Check it before trying another transaction.';
|
||||
|
||||
@override
|
||||
String get wCheckOutcome => 'Check outcome';
|
||||
|
||||
@override
|
||||
String get wNoSubmission =>
|
||||
'No transaction was found. You can review and confirm again.';
|
||||
|
||||
@override
|
||||
String get wDiscardTitle => 'Leave this transaction?';
|
||||
|
||||
@override
|
||||
String get wDiscardBody =>
|
||||
'Your entered details will be cleared. No new transaction will be submitted.';
|
||||
|
||||
@override
|
||||
String get wStay => 'Keep editing';
|
||||
|
||||
@override
|
||||
String get wAll => 'All';
|
||||
|
||||
@override
|
||||
String get wSearch => 'Search by reference';
|
||||
|
||||
@override
|
||||
String get wNoMatches => 'No matching transactions. Try another filter.';
|
||||
|
||||
@override
|
||||
String get wClearFilters => 'Clear filters';
|
||||
|
||||
@override
|
||||
String get wReference => 'Reference';
|
||||
|
||||
@override
|
||||
String get wWhen => 'Created';
|
||||
|
||||
@override
|
||||
String get wViewAccount => 'View account';
|
||||
|
||||
@override
|
||||
String get wPreferences => 'Make it yours.';
|
||||
|
||||
@override
|
||||
String get wLanguage => 'Account language';
|
||||
|
||||
@override
|
||||
String get wLanguageHelp => 'Used throughout your account.';
|
||||
|
||||
@override
|
||||
String get wLanguageSaved => 'Saved to your account.';
|
||||
|
||||
@override
|
||||
String get wLanguageFailed =>
|
||||
'Changed for this session, but not saved to your account. Retry below.';
|
||||
|
||||
@override
|
||||
String get wProfileError =>
|
||||
'Account preferences could not be loaded. Try again.';
|
||||
|
||||
@override
|
||||
String get wSecurity => 'Security';
|
||||
|
||||
@override
|
||||
String get wSecurityBody =>
|
||||
'Password and security settings are not available yet.';
|
||||
|
||||
@override
|
||||
String get wIdentity => 'Identity verification';
|
||||
|
||||
@override
|
||||
String get wIdentityHelp => 'Optional verification for higher limits.';
|
||||
|
||||
@override
|
||||
String get wBackLogin => 'Back to login';
|
||||
|
||||
@override
|
||||
String get wEnter => 'Explore your accounts';
|
||||
|
||||
@override
|
||||
String get wUsage => 'Used this period';
|
||||
|
||||
@override
|
||||
String get wLimitsScope => 'Outgoing activity · EUR equivalent';
|
||||
|
||||
@override
|
||||
String get wPendingHint =>
|
||||
'Refresh to check the latest status. You can keep using your other accounts.';
|
||||
|
||||
@override
|
||||
String get wFundsHeading => 'Where is my money?';
|
||||
|
||||
@override
|
||||
String get wAnother => 'Make another transaction';
|
||||
|
||||
@override
|
||||
String get factorTitle => 'One more check.';
|
||||
|
||||
@override
|
||||
String get factorLogin => 'Confirm it’s you to finish signing in.';
|
||||
|
||||
@override
|
||||
String get factorMoney =>
|
||||
'Confirm this transaction. No money moves before confirmation.';
|
||||
|
||||
@override
|
||||
String get factorEnableHelp =>
|
||||
'Confirm to turn on two-factor verification for sign-in and money operations.';
|
||||
|
||||
@override
|
||||
String get factorDisableHelp =>
|
||||
'Confirm to turn off two-factor verification. Sign-in and money operations will no longer ask for a second code.';
|
||||
|
||||
@override
|
||||
String get factorEmail => 'Enter the six-digit code sent to your email.';
|
||||
|
||||
@override
|
||||
String get factorAuthenticator =>
|
||||
'Enter the current six-digit code from your authenticator app.';
|
||||
|
||||
@override
|
||||
String get factorCode => 'Verification code';
|
||||
|
||||
@override
|
||||
String get factorConfirm => 'Verify and continue';
|
||||
|
||||
@override
|
||||
String get factorResend => 'Send a new code';
|
||||
|
||||
@override
|
||||
String get factorHelp =>
|
||||
'Check your inbox and spam folder. A new code replaces the previous one.';
|
||||
|
||||
@override
|
||||
String get factorAppHelp =>
|
||||
'Use the current code. If you cannot access your authenticator, cancel this action.';
|
||||
|
||||
@override
|
||||
String get factorInvalid =>
|
||||
'That code is not valid. Check all six digits and try again.';
|
||||
|
||||
@override
|
||||
String get factorExpired =>
|
||||
'This verification has expired. Request a new code or return to start again.';
|
||||
|
||||
@override
|
||||
String get factorLocked =>
|
||||
'Too many incorrect attempts. Wait five minutes, then start verification again.';
|
||||
|
||||
@override
|
||||
String get factorWait => 'Please wait before requesting another code.';
|
||||
|
||||
@override
|
||||
String get factorRequired =>
|
||||
'Verification is needed again. Please confirm this action once more.';
|
||||
|
||||
@override
|
||||
String get factorUnavailable =>
|
||||
'Could not complete verification. Try again or cancel this action.';
|
||||
|
||||
@override
|
||||
String factorCooldown(int seconds) {
|
||||
return 'Send a new code in ${seconds}s';
|
||||
}
|
||||
|
||||
@override
|
||||
String get factorSettings => 'Two-factor verification';
|
||||
|
||||
@override
|
||||
String get factorSettingsHelp =>
|
||||
'Add a verification code when you sign in, top up, exchange or pay out.';
|
||||
|
||||
@override
|
||||
String get factorOn => 'On';
|
||||
|
||||
@override
|
||||
String get factorOff => 'Off';
|
||||
|
||||
@override
|
||||
String get factorEnable => 'Turn on';
|
||||
|
||||
@override
|
||||
String get factorDisable => 'Turn off';
|
||||
|
||||
@override
|
||||
String get factorEmailMethod => 'Email code';
|
||||
|
||||
@override
|
||||
String get factorAppMethod => 'Authenticator app';
|
||||
|
||||
@override
|
||||
String get factorAppExpired =>
|
||||
'This verification has expired. Return and start again.';
|
||||
}
|
||||
@@ -1,857 +0,0 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Russian (`ru`).
|
||||
class AppLocalizationsRu extends AppLocalizations {
|
||||
AppLocalizationsRu([String locale = 'ru']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'SXCHNGE · Ваш следующий шаг';
|
||||
|
||||
@override
|
||||
String get nextChapter => 'НОВАЯ ГЛАВА';
|
||||
|
||||
@override
|
||||
String get loginHeading => 'Рады видеть\nвас снова.';
|
||||
|
||||
@override
|
||||
String get signupHeading => 'Сделайте\nпервый шаг.';
|
||||
|
||||
@override
|
||||
String get loginIntro => 'Войдите, чтобы сделать следующий шаг.';
|
||||
|
||||
@override
|
||||
String get signupIntro => 'Новый аккаунт. Новые возможности.';
|
||||
|
||||
@override
|
||||
String get login => 'Войти';
|
||||
|
||||
@override
|
||||
String get signup => 'Регистрация';
|
||||
|
||||
@override
|
||||
String get name => 'Ваше имя';
|
||||
|
||||
@override
|
||||
String get email => 'Электронная почта';
|
||||
|
||||
@override
|
||||
String get emailHint => 'you@example.com';
|
||||
|
||||
@override
|
||||
String get password => 'Пароль';
|
||||
|
||||
@override
|
||||
String get confirmPassword => 'Повторите пароль';
|
||||
|
||||
@override
|
||||
String get passwordHint => 'Используйте не менее 12 символов.';
|
||||
|
||||
@override
|
||||
String get showPassword => 'Показать пароль';
|
||||
|
||||
@override
|
||||
String get hidePassword => 'Скрыть пароль';
|
||||
|
||||
@override
|
||||
String get showConfirmation => 'Показать повтор пароля';
|
||||
|
||||
@override
|
||||
String get hideConfirmation => 'Скрыть повтор пароля';
|
||||
|
||||
@override
|
||||
String get createAccount => 'Создать аккаунт';
|
||||
|
||||
@override
|
||||
String get letsGo => 'Продолжить';
|
||||
|
||||
@override
|
||||
String get loginPreview => 'Вы вошли в аккаунт.';
|
||||
|
||||
@override
|
||||
String get signupPreview => 'Ваш аккаунт готов.';
|
||||
|
||||
@override
|
||||
String get alreadyMember => 'Уже есть аккаунт sxchnge?';
|
||||
|
||||
@override
|
||||
String get newHere => 'Впервые здесь?';
|
||||
|
||||
@override
|
||||
String get loginInstead => 'Войти в аккаунт';
|
||||
|
||||
@override
|
||||
String get createAnAccount => 'Зарегистрироваться';
|
||||
|
||||
@override
|
||||
String get brandEyebrow => 'МАЛЕНЬКАЯ ПЕРЕМЕНА. БОЛЬШОЙ ШАГ.';
|
||||
|
||||
@override
|
||||
String get brandHeading => 'Всё начинается\nс перемен.';
|
||||
|
||||
@override
|
||||
String get brandIntro =>
|
||||
'Новый путь вперёд.\nОткройте его вместе со sxchnge.';
|
||||
|
||||
@override
|
||||
String get brandCard => 'Меньше преград.\nБольше возможностей.';
|
||||
|
||||
@override
|
||||
String get brandFooter => 'СЛЕДУЮЩИЙ ШАГ — ЗА ВАМИ.';
|
||||
|
||||
@override
|
||||
String get emailRequired => 'Введите электронную почту.';
|
||||
|
||||
@override
|
||||
String get emailInvalid => 'Введите корректный адрес почты.';
|
||||
|
||||
@override
|
||||
String get nameRequired => 'Введите ваше имя.';
|
||||
|
||||
@override
|
||||
String get passwordRequired => 'Введите пароль.';
|
||||
|
||||
@override
|
||||
String get confirmationRequired => 'Повторите пароль.';
|
||||
|
||||
@override
|
||||
String get passwordMismatch => 'Пароли не совпадают.';
|
||||
|
||||
@override
|
||||
String get language => 'Язык';
|
||||
|
||||
@override
|
||||
String get preferenceNotSaved =>
|
||||
'Язык изменён для этого сеанса. Сохранить выбор не удалось.';
|
||||
|
||||
@override
|
||||
String get authWorking => 'Подождите…';
|
||||
|
||||
@override
|
||||
String get authCompleted => 'Запрос выполнен.';
|
||||
|
||||
@override
|
||||
String get authInvalidCredentials =>
|
||||
'Неверная почта или пароль. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get authEmailInUse =>
|
||||
'Эта почта уже зарегистрирована. Попробуйте войти.';
|
||||
|
||||
@override
|
||||
String get authUnavailable => 'Сервис недоступен. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get authUnexpected => 'Что-то пошло не так. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get checkInbox => 'Проверьте почту.';
|
||||
|
||||
@override
|
||||
String get verificationSent =>
|
||||
'Мы отправили вам письмо для подтверждения. Откройте ссылку в письме, чтобы подтвердить электронную почту.';
|
||||
|
||||
@override
|
||||
String get verificationSending => 'Готовим письмо для подтверждения…';
|
||||
|
||||
@override
|
||||
String get verificationChecking => 'Подтверждаем вашу почту…';
|
||||
|
||||
@override
|
||||
String get verificationVerified => 'Ваша почта\nподтверждена.';
|
||||
|
||||
@override
|
||||
String get verificationVerifiedBody =>
|
||||
'Первый шаг пройден. Теперь можно вернуться ко входу. Подтверждение личности — отдельный этап.';
|
||||
|
||||
@override
|
||||
String get verificationExpired => 'Срок действия ссылки истёк.';
|
||||
|
||||
@override
|
||||
String get verificationExpiredBody =>
|
||||
'Запросите новое письмо и перейдите по ссылке из него.';
|
||||
|
||||
@override
|
||||
String get verificationInvalid => 'Ссылка недействительна.';
|
||||
|
||||
@override
|
||||
String get verificationInvalidBody =>
|
||||
'Ссылка подтверждения отсутствует или недействительна. Откройте полную ссылку из письма или запросите новую.';
|
||||
|
||||
@override
|
||||
String get verificationInternal => 'Что-то пошло не так.';
|
||||
|
||||
@override
|
||||
String get verificationInternalBody =>
|
||||
'Не удалось выполнить запрос из-за внутренней ошибки. Попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get verificationResend => 'Отправить письмо ещё раз';
|
||||
|
||||
@override
|
||||
String get verificationRequest => 'Отправить новое письмо';
|
||||
|
||||
@override
|
||||
String get verificationRetry => 'Повторить подтверждение';
|
||||
|
||||
@override
|
||||
String get verificationBack => 'Вернуться ко входу';
|
||||
|
||||
@override
|
||||
String get verificationHint =>
|
||||
'Нет письма? Проверьте папку «Спам» или запросите новое ниже.';
|
||||
|
||||
@override
|
||||
String get verificationDemoLink => 'Перейти к подтверждению';
|
||||
|
||||
@override
|
||||
String get verificationStep => 'ПОДТВЕРДИТЕ ПОЧТУ';
|
||||
|
||||
@override
|
||||
String get verificationRecipient => 'Получатель';
|
||||
|
||||
@override
|
||||
String get verificationChangeEmail => 'Изменить почту';
|
||||
|
||||
@override
|
||||
String get kycTitle => 'Верификация и лимиты';
|
||||
|
||||
@override
|
||||
String get kycIntro =>
|
||||
'Ваш аккаунт — ваш темп. Подтвердите личность, когда понадобятся более высокие лимиты.';
|
||||
|
||||
@override
|
||||
String get kycUnverified => 'Личность не подтверждена';
|
||||
|
||||
@override
|
||||
String get kycPending => 'На проверке';
|
||||
|
||||
@override
|
||||
String get kycApproved => 'Личность подтверждена';
|
||||
|
||||
@override
|
||||
String get kycChanges => 'Нужны новые фото';
|
||||
|
||||
@override
|
||||
String get kycDenied => 'Верификация отклонена';
|
||||
|
||||
@override
|
||||
String get kycUnverifiedBody =>
|
||||
'Вы можете пользоваться аккаунтом с базовыми лимитами. Для повышения отправьте фото паспорта и селфи с ним.';
|
||||
|
||||
@override
|
||||
String get kycPendingBody =>
|
||||
'Заявка ожидает проверки сотрудником. До одобрения действуют базовые лимиты. Повторная отправка не нужна.';
|
||||
|
||||
@override
|
||||
String get kycApprovedBody =>
|
||||
'Проверка завершена. Актуальные лимиты указаны ниже.';
|
||||
|
||||
@override
|
||||
String get kycChangesBody =>
|
||||
'Аккаунт доступен с базовыми лимитами. Замените фото и отправьте новую заявку.';
|
||||
|
||||
@override
|
||||
String get kycDeniedBody =>
|
||||
'Базовые лимиты остаются доступными. Если вы не согласны с решением, обратитесь в поддержку для пересмотра.';
|
||||
|
||||
@override
|
||||
String get kycPassportReason =>
|
||||
'Данные паспорта не удалось прочитать. Переснимите при хорошем освещении, чтобы все края были видны.';
|
||||
|
||||
@override
|
||||
String get kycSelfieReason =>
|
||||
'На селфи плохо видно лицо или паспорт. Оба должны быть в кадре и в фокусе.';
|
||||
|
||||
@override
|
||||
String get kycExpiredReason =>
|
||||
'Срок действия паспорта истёк. Используйте действующий паспорт и сделайте оба фото заново.';
|
||||
|
||||
@override
|
||||
String get kycDeniedReason =>
|
||||
'По этой заявке не удалось подтвердить вашу личность.';
|
||||
|
||||
@override
|
||||
String get kycStart => 'Подтвердить личность';
|
||||
|
||||
@override
|
||||
String get kycResubmit => 'Заменить фото';
|
||||
|
||||
@override
|
||||
String get kycRefresh => 'Обновить статус и лимиты';
|
||||
|
||||
@override
|
||||
String get kycSupport => 'Обратиться в поддержку';
|
||||
|
||||
@override
|
||||
String get kycSupportBody => 'Поддержка сейчас недоступна. Попробуйте позже.';
|
||||
|
||||
@override
|
||||
String get kycBasic => 'Базовый';
|
||||
|
||||
@override
|
||||
String get kycVerifiedTier => 'Подтверждённый';
|
||||
|
||||
@override
|
||||
String get kycLimits => 'Ваши лимиты';
|
||||
|
||||
@override
|
||||
String get kycDay => 'Объём за день';
|
||||
|
||||
@override
|
||||
String get kycMonth => 'Объём за месяц';
|
||||
|
||||
@override
|
||||
String get kycCurrent => 'Текущий лимит';
|
||||
|
||||
@override
|
||||
String get kycRemaining => 'Доступно';
|
||||
|
||||
@override
|
||||
String get kycAfter => 'После одобрения';
|
||||
|
||||
@override
|
||||
String get kycLimitsError =>
|
||||
'Не удалось обновить лимиты. Значения ниже могут быть устаревшими. Повторите запрос.';
|
||||
|
||||
@override
|
||||
String get kycLimitsEmpty =>
|
||||
'Сервис не вернул лимиты. Обновите данные или уточните их в поддержке перед операцией.';
|
||||
|
||||
@override
|
||||
String get kycLoading => 'Загружаем ваш статус…';
|
||||
|
||||
@override
|
||||
String get kycPassport => 'Фото паспорта';
|
||||
|
||||
@override
|
||||
String get kycSelfie => 'Селфи с паспортом';
|
||||
|
||||
@override
|
||||
String get kycReview => 'Проверьте фото';
|
||||
|
||||
@override
|
||||
String get kycPassportHelp =>
|
||||
'Откройте действующий паспорт на странице с фото. Все четыре края и текст должны быть чётко видны. Избегайте бликов и размытия, не закрывайте данные.';
|
||||
|
||||
@override
|
||||
String get kycSelfieHelp =>
|
||||
'Держите открытый паспорт рядом с лицом. Лицо целиком и страница с фото должны быть в одном кадре, чёткими и ничем не закрытыми.';
|
||||
|
||||
@override
|
||||
String get kycFileRules =>
|
||||
'JPEG или PNG · до 10 МБ · не менее 600 × 600 пикселей. Сделайте фото камерой, затем выберите его здесь.';
|
||||
|
||||
@override
|
||||
String get kycChoose => 'Выбрать фото';
|
||||
|
||||
@override
|
||||
String get kycReplace => 'Выбрать другое фото';
|
||||
|
||||
@override
|
||||
String get kycRemove => 'Удалить фото';
|
||||
|
||||
@override
|
||||
String get kycNext => 'Продолжить';
|
||||
|
||||
@override
|
||||
String get kycBack => 'Назад';
|
||||
|
||||
@override
|
||||
String get kycSkip => 'Оставить базовые лимиты';
|
||||
|
||||
@override
|
||||
String get kycClose => 'К аккаунту';
|
||||
|
||||
@override
|
||||
String get kycReviewHelp =>
|
||||
'Убедитесь, что оба фото читаемы и на них один паспорт. Их проверит сотрудник. Отправка не повышает лимиты сразу.';
|
||||
|
||||
@override
|
||||
String get kycConfirm => 'На фото я и мой действующий паспорт.';
|
||||
|
||||
@override
|
||||
String get kycSubmit => 'Отправить на проверку';
|
||||
|
||||
@override
|
||||
String get kycWorking => 'Обработка… Не закрывайте страницу.';
|
||||
|
||||
@override
|
||||
String get kycDiscardTitle => 'Удалить выбранные фото?';
|
||||
|
||||
@override
|
||||
String get kycDiscardBody =>
|
||||
'Черновик хранится только в памяти. При выходе выбранные фото будут удалены. Позже можно начать заново.';
|
||||
|
||||
@override
|
||||
String get kycStay => 'Продолжить';
|
||||
|
||||
@override
|
||||
String get kycDiscard => 'Удалить и выйти';
|
||||
|
||||
@override
|
||||
String get kycErrorUnavailable =>
|
||||
'Сервис недоступен. Выбранные фото сохранены в памяти. Повторите попытку.';
|
||||
|
||||
@override
|
||||
String get kycErrorInternal =>
|
||||
'Произошла ошибка. Повторите попытку; выбранные фото сохранены.';
|
||||
|
||||
@override
|
||||
String get kycErrorPermission =>
|
||||
'Не удалось открыть фото. Проверьте доступ к файлам или выберите другое изображение.';
|
||||
|
||||
@override
|
||||
String get kycErrorFormat =>
|
||||
'Выберите изображение JPEG или PNG. Другие форматы не поддерживаются.';
|
||||
|
||||
@override
|
||||
String get kycErrorLarge =>
|
||||
'Изображение слишком большое. Выберите файл до 10 МБ и 40 мегапикселей.';
|
||||
|
||||
@override
|
||||
String get kycErrorUnreadable =>
|
||||
'Изображение повреждено или не читается. Выберите другое фото.';
|
||||
|
||||
@override
|
||||
String get kycErrorSmall =>
|
||||
'Фото слишком маленькое. Ширина и высота должны быть не менее 600 пикселей.';
|
||||
|
||||
@override
|
||||
String get kycErrorIncomplete =>
|
||||
'Выберите оба фото и подтвердите, что на них вы и ваш паспорт.';
|
||||
|
||||
@override
|
||||
String get kycErrorSession =>
|
||||
'Сессия истекла. Войдите снова и проверьте статус верификации.';
|
||||
|
||||
@override
|
||||
String get kycErrorRate =>
|
||||
'Слишком много запросов. Немного подождите и повторите попытку.';
|
||||
|
||||
@override
|
||||
String get kycErrorConflict =>
|
||||
'Статус проверки изменился. Обновите его, прежде чем продолжить.';
|
||||
|
||||
@override
|
||||
String get kycDemoControls => 'Сценарии проверки';
|
||||
|
||||
@override
|
||||
String get kycDemoApprove => 'Одобрить';
|
||||
|
||||
@override
|
||||
String get kycDemoCorrect => 'Запросить чёткое фото паспорта';
|
||||
|
||||
@override
|
||||
String get kycDemoSelfie => 'Запросить чёткое селфи';
|
||||
|
||||
@override
|
||||
String get kycDemoExpired => 'Паспорт просрочен';
|
||||
|
||||
@override
|
||||
String get kycDemoDeny => 'Отклонить';
|
||||
|
||||
@override
|
||||
String get kycPreview => 'Посмотреть верификацию и лимиты';
|
||||
|
||||
@override
|
||||
String get kycChooseFirst => 'Выберите фото для продолжения.';
|
||||
|
||||
@override
|
||||
String get kycStatusStale =>
|
||||
'Не удалось обновить статус. Показан последний известный результат.';
|
||||
|
||||
@override
|
||||
String get kycUsed => 'Использовано';
|
||||
|
||||
@override
|
||||
String get kycReserved => 'Зарезервировано';
|
||||
|
||||
@override
|
||||
String get kycUsageHelp =>
|
||||
'Использовано — завершённые операции. Зарезервировано — суммы, удержанные для ожидающих операций. Обе суммы учитываются в лимите.';
|
||||
|
||||
@override
|
||||
String get kycOccupied => 'Использовано + зарезервировано';
|
||||
|
||||
@override
|
||||
String get kycNoCapacity => 'Доступный объём исчерпан';
|
||||
|
||||
@override
|
||||
String get wAccounts => 'Счета';
|
||||
|
||||
@override
|
||||
String get wActivity => 'История';
|
||||
|
||||
@override
|
||||
String get wLimits => 'Лимиты';
|
||||
|
||||
@override
|
||||
String get wSettings => 'Настройки';
|
||||
|
||||
@override
|
||||
String get wHeadline => 'Ваши деньги.\nВаш следующий шаг.';
|
||||
|
||||
@override
|
||||
String get wSubheading => 'Пополняйте. Обменивайте. Двигайтесь дальше.';
|
||||
|
||||
@override
|
||||
String get wAvailable => 'Вам доступно';
|
||||
|
||||
@override
|
||||
String get wTopUp => 'Пополнить';
|
||||
|
||||
@override
|
||||
String get wExchange => 'Обменять';
|
||||
|
||||
@override
|
||||
String get wPayout => 'Вывести';
|
||||
|
||||
@override
|
||||
String get wRecent => 'Последние операции';
|
||||
|
||||
@override
|
||||
String get wSeeAll => 'Смотреть все';
|
||||
|
||||
@override
|
||||
String get wEmptyActivity =>
|
||||
'Ваш следующий шаг начинается здесь. Здесь появятся ваши операции.';
|
||||
|
||||
@override
|
||||
String get wEmptyAccounts =>
|
||||
'Ваши счета ещё не готовы. Попробуйте обновить страницу.';
|
||||
|
||||
@override
|
||||
String get wRefresh => 'Обновить';
|
||||
|
||||
@override
|
||||
String get wSource => 'Со счёта';
|
||||
|
||||
@override
|
||||
String get wDestination => 'На счёт';
|
||||
|
||||
@override
|
||||
String get wReceiveAccount => 'Счёт для зачисления';
|
||||
|
||||
@override
|
||||
String get wAmount => 'Сумма';
|
||||
|
||||
@override
|
||||
String get wRecipient => 'Получатель';
|
||||
|
||||
@override
|
||||
String get wRecipientHint => 'Имя или реквизиты получателя';
|
||||
|
||||
@override
|
||||
String get wReview => 'Проверить';
|
||||
|
||||
@override
|
||||
String get wReviewTitle => 'Проверьте детали.';
|
||||
|
||||
@override
|
||||
String get wFee => 'Комиссия';
|
||||
|
||||
@override
|
||||
String get wTotalDebit => 'Спишется со счёта';
|
||||
|
||||
@override
|
||||
String get wCredit => 'Поступит на счёт';
|
||||
|
||||
@override
|
||||
String get wRecipientGets => 'Получатель получит';
|
||||
|
||||
@override
|
||||
String get wYouPay => 'Вы платите';
|
||||
|
||||
@override
|
||||
String get wRate => 'Курс обмена';
|
||||
|
||||
@override
|
||||
String get wQuoteNotice =>
|
||||
'Эти условия действуют 60 секунд. Подтвердите их, прежде чем продолжить.';
|
||||
|
||||
@override
|
||||
String get wExpired =>
|
||||
'Срок действия расчёта истёк. Обновите его и проверьте новые условия.';
|
||||
|
||||
@override
|
||||
String get wNewQuote => 'Обновить расчёт';
|
||||
|
||||
@override
|
||||
String get wConfirm => 'Подтвердить операцию';
|
||||
|
||||
@override
|
||||
String get wEdit => 'Изменить данные';
|
||||
|
||||
@override
|
||||
String get wClose => 'Закрыть';
|
||||
|
||||
@override
|
||||
String get wWorking => 'Обрабатываем…';
|
||||
|
||||
@override
|
||||
String get wPending => 'В обработке';
|
||||
|
||||
@override
|
||||
String get wCompleted => 'Завершено';
|
||||
|
||||
@override
|
||||
String get wFailed => 'Не выполнено';
|
||||
|
||||
@override
|
||||
String get wPendingTopUp =>
|
||||
'Пополнение обрабатывается. Доступный баланс пока не изменился.';
|
||||
|
||||
@override
|
||||
String get wPendingExchange =>
|
||||
'Сумма и комиссия уже вычтены из доступного баланса. После завершения обмена деньги появятся на счёте зачисления.';
|
||||
|
||||
@override
|
||||
String get wPendingPayout =>
|
||||
'Сумма и комиссия уже вычтены из доступного баланса. Вывод ещё обрабатывается.';
|
||||
|
||||
@override
|
||||
String get wCompleteTopUp => 'Счёт пополнен. Деньги доступны для операций.';
|
||||
|
||||
@override
|
||||
String get wCompleteExchange =>
|
||||
'Обмен завершён. Деньги доступны на счёте зачисления.';
|
||||
|
||||
@override
|
||||
String get wCompletePayout =>
|
||||
'Вывод завершён. Сумма и комиссия списаны с вашего счёта.';
|
||||
|
||||
@override
|
||||
String get wFailedTopUp =>
|
||||
'Пополнение не выполнено. На счёт ничего не зачислено.';
|
||||
|
||||
@override
|
||||
String get wFailedOutgoing =>
|
||||
'Операция не выполнена. Сумма и комиссия снова доступны на вашем счёте.';
|
||||
|
||||
@override
|
||||
String get wUnavailable =>
|
||||
'Не удалось обновить данные. Информация может быть устаревшей. Повторите попытку.';
|
||||
|
||||
@override
|
||||
String get wInvalidAmount =>
|
||||
'Введите сумму больше нуля с допустимым для валюты числом знаков после запятой.';
|
||||
|
||||
@override
|
||||
String get wInsufficient =>
|
||||
'Для этой суммы и комиссии недостаточно доступных средств. Пополните счёт или уменьшите сумму.';
|
||||
|
||||
@override
|
||||
String get wLimitError =>
|
||||
'Сумма превышает доступный лимит. Проверьте лимиты перед продолжением.';
|
||||
|
||||
@override
|
||||
String get wRecipientError =>
|
||||
'Введите тестового получателя длиной от 6 до 140 символов.';
|
||||
|
||||
@override
|
||||
String get wUnsupported => 'Эта комбинация недоступна. Выберите другой счёт.';
|
||||
|
||||
@override
|
||||
String get wNotFound => 'Не удалось найти запись. Вернитесь к счетам.';
|
||||
|
||||
@override
|
||||
String get wUncertain =>
|
||||
'Не удалось подтвердить результат. Проверьте его, прежде чем создавать другую операцию.';
|
||||
|
||||
@override
|
||||
String get wCheckOutcome => 'Проверить результат';
|
||||
|
||||
@override
|
||||
String get wNoSubmission =>
|
||||
'Операция не найдена. Вы можете снова проверить и подтвердить её.';
|
||||
|
||||
@override
|
||||
String get wDiscardTitle => 'Выйти из операции?';
|
||||
|
||||
@override
|
||||
String get wDiscardBody =>
|
||||
'Введённые данные будут удалены. Новая операция не будет отправлена.';
|
||||
|
||||
@override
|
||||
String get wStay => 'Продолжить';
|
||||
|
||||
@override
|
||||
String get wAll => 'Все';
|
||||
|
||||
@override
|
||||
String get wSearch => 'Поиск по номеру';
|
||||
|
||||
@override
|
||||
String get wNoMatches => 'Операции не найдены. Измените фильтр.';
|
||||
|
||||
@override
|
||||
String get wClearFilters => 'Сбросить фильтры';
|
||||
|
||||
@override
|
||||
String get wReference => 'Номер операции';
|
||||
|
||||
@override
|
||||
String get wWhen => 'Создано';
|
||||
|
||||
@override
|
||||
String get wViewAccount => 'Открыть счёт';
|
||||
|
||||
@override
|
||||
String get wPreferences => 'Настройте под себя.';
|
||||
|
||||
@override
|
||||
String get wLanguage => 'Язык аккаунта';
|
||||
|
||||
@override
|
||||
String get wLanguageHelp => 'Используется во всём аккаунте.';
|
||||
|
||||
@override
|
||||
String get wLanguageSaved => 'Сохранено в аккаунте.';
|
||||
|
||||
@override
|
||||
String get wLanguageFailed =>
|
||||
'Изменено для текущей сессии, но не сохранено в аккаунте. Повторите попытку ниже.';
|
||||
|
||||
@override
|
||||
String get wProfileError =>
|
||||
'Не удалось загрузить настройки аккаунта. Повторите попытку.';
|
||||
|
||||
@override
|
||||
String get wSecurity => 'Безопасность';
|
||||
|
||||
@override
|
||||
String get wSecurityBody =>
|
||||
'Настройки пароля и безопасности пока недоступны.';
|
||||
|
||||
@override
|
||||
String get wIdentity => 'Подтверждение личности';
|
||||
|
||||
@override
|
||||
String get wIdentityHelp => 'Необязательная проверка для повышения лимитов.';
|
||||
|
||||
@override
|
||||
String get wBackLogin => 'Вернуться ко входу';
|
||||
|
||||
@override
|
||||
String get wEnter => 'Открыть свои счета';
|
||||
|
||||
@override
|
||||
String get wUsage => 'Использовано за период';
|
||||
|
||||
@override
|
||||
String get wLimitsScope => 'Исходящие операции · эквивалент в EUR';
|
||||
|
||||
@override
|
||||
String get wPendingHint =>
|
||||
'Обновите, чтобы узнать текущий статус. Остальными счетами можно пользоваться.';
|
||||
|
||||
@override
|
||||
String get wFundsHeading => 'Где мои деньги?';
|
||||
|
||||
@override
|
||||
String get wAnother => 'Создать новую операцию';
|
||||
|
||||
@override
|
||||
String get factorTitle => 'Ещё одна проверка.';
|
||||
|
||||
@override
|
||||
String get factorLogin => 'Подтвердите, что это вы, чтобы завершить вход.';
|
||||
|
||||
@override
|
||||
String get factorMoney =>
|
||||
'Подтвердите эту операцию. До подтверждения деньги не переводятся.';
|
||||
|
||||
@override
|
||||
String get factorEnableHelp =>
|
||||
'Подтвердите включение двухфакторной проверки для входа и операций с деньгами.';
|
||||
|
||||
@override
|
||||
String get factorDisableHelp =>
|
||||
'Подтвердите отключение двухфакторной проверки. Для входа и операций больше не потребуется второй код.';
|
||||
|
||||
@override
|
||||
String get factorEmail => 'Введите шестизначный код из письма.';
|
||||
|
||||
@override
|
||||
String get factorAuthenticator =>
|
||||
'Введите текущий шестизначный код из приложения-аутентификатора.';
|
||||
|
||||
@override
|
||||
String get factorCode => 'Код подтверждения';
|
||||
|
||||
@override
|
||||
String get factorConfirm => 'Подтвердить и продолжить';
|
||||
|
||||
@override
|
||||
String get factorResend => 'Отправить новый код';
|
||||
|
||||
@override
|
||||
String get factorHelp =>
|
||||
'Проверьте входящие и папку «Спам». Новый код заменяет предыдущий.';
|
||||
|
||||
@override
|
||||
String get factorAppHelp =>
|
||||
'Используйте текущий код. Если приложение недоступно, отмените это действие.';
|
||||
|
||||
@override
|
||||
String get factorInvalid =>
|
||||
'Неверный код. Проверьте все шесть цифр и попробуйте ещё раз.';
|
||||
|
||||
@override
|
||||
String get factorExpired =>
|
||||
'Срок проверки истёк. Запросите новый код или вернитесь и начните снова.';
|
||||
|
||||
@override
|
||||
String get factorLocked =>
|
||||
'Слишком много неверных попыток. Подождите пять минут и начните проверку снова.';
|
||||
|
||||
@override
|
||||
String get factorWait => 'Подождите перед повторным запросом кода.';
|
||||
|
||||
@override
|
||||
String get factorRequired =>
|
||||
'Нужна повторная проверка. Подтвердите это действие ещё раз.';
|
||||
|
||||
@override
|
||||
String get factorUnavailable =>
|
||||
'Не удалось завершить проверку. Повторите попытку или отмените действие.';
|
||||
|
||||
@override
|
||||
String factorCooldown(int seconds) {
|
||||
return 'Новый код через $seconds с';
|
||||
}
|
||||
|
||||
@override
|
||||
String get factorSettings => 'Двухфакторная проверка';
|
||||
|
||||
@override
|
||||
String get factorSettingsHelp =>
|
||||
'Подтверждайте кодом вход, пополнение, обмен и вывод средств.';
|
||||
|
||||
@override
|
||||
String get factorOn => 'Включена';
|
||||
|
||||
@override
|
||||
String get factorOff => 'Выключена';
|
||||
|
||||
@override
|
||||
String get factorEnable => 'Включить';
|
||||
|
||||
@override
|
||||
String get factorDisable => 'Выключить';
|
||||
|
||||
@override
|
||||
String get factorEmailMethod => 'Код из письма';
|
||||
|
||||
@override
|
||||
String get factorAppMethod => 'Приложение-аутентификатор';
|
||||
|
||||
@override
|
||||
String get factorAppExpired =>
|
||||
'Срок проверки истёк. Вернитесь и начните снова.';
|
||||
}
|
||||
@@ -43,10 +43,11 @@
|
||||
"authCompleted": "Request completed.",
|
||||
"authInvalidCredentials": "Email or password is incorrect. Please try again.",
|
||||
"authEmailInUse": "This email is already registered. Try logging in.",
|
||||
"authVerifyEmail": "Confirm your email before signing in.",
|
||||
"authUnavailable": "The service is unavailable. Please try again.",
|
||||
"authUnexpected": "Something went wrong. Please try again.",
|
||||
"checkInbox": "Check your inbox.",
|
||||
"verificationSent": "We’ve sent you a verification email. Open the link in your inbox to confirm your email address.",
|
||||
"verificationSent": "If this email belongs to an unverified account, a verification email is on its way. Open the link in your inbox to confirm your address.",
|
||||
"verificationSending": "Preparing your verification email…",
|
||||
"verificationChecking": "Verifying your email…",
|
||||
"verificationVerified": "Your email\nis verified.",
|
||||
|
||||
@@ -43,10 +43,11 @@
|
||||
"authCompleted": "Запрос выполнен.",
|
||||
"authInvalidCredentials": "Неверная почта или пароль. Попробуйте ещё раз.",
|
||||
"authEmailInUse": "Эта почта уже зарегистрирована. Попробуйте войти.",
|
||||
"authVerifyEmail": "Подтвердите почту перед входом.",
|
||||
"authUnavailable": "Сервис недоступен. Попробуйте ещё раз.",
|
||||
"authUnexpected": "Что-то пошло не так. Попробуйте ещё раз.",
|
||||
"checkInbox": "Проверьте почту.",
|
||||
"verificationSent": "Мы отправили вам письмо для подтверждения. Откройте ссылку в письме, чтобы подтвердить электронную почту.",
|
||||
"verificationSent": "Если эта почта принадлежит неподтверждённому аккаунту, письмо с подтверждением уже в пути. Откройте ссылку в письме, чтобы подтвердить адрес.",
|
||||
"verificationSending": "Готовим письмо для подтверждения…",
|
||||
"verificationChecking": "Подтверждаем вашу почту…",
|
||||
"verificationVerified": "Ваша почта\nподтверждена.",
|
||||
|
||||
@@ -20,13 +20,19 @@ import 'generated/i18n/app_localizations.dart';
|
||||
Future<void> main() async {
|
||||
final binding = WidgetsFlutterBinding.ensureInitialized();
|
||||
final locale = await LocaleProvider.load(binding.platformDispatcher.locales);
|
||||
runApp(SxchngeApp(localeProvider: locale));
|
||||
const apiUrl = String.fromEnvironment('SXCHNGE_API_URL');
|
||||
const clientId = String.fromEnvironment('SXCHNGE_CLIENT_ID');
|
||||
final backend = apiUrl.isEmpty
|
||||
? null
|
||||
: BackendAuthServices(baseUri: Uri.parse(apiUrl), clientId: clientId);
|
||||
runApp(SxchngeApp(localeProvider: locale, backend: backend));
|
||||
}
|
||||
|
||||
class SxchngeApp extends StatefulWidget {
|
||||
const SxchngeApp({
|
||||
super.key,
|
||||
this.localeProvider,
|
||||
this.backend,
|
||||
this.analyticsService,
|
||||
this.identityService,
|
||||
this.limitsService,
|
||||
@@ -38,6 +44,7 @@ class SxchngeApp extends StatefulWidget {
|
||||
this.verificationService = const DummyVerificationService(),
|
||||
});
|
||||
final LocaleProvider? localeProvider;
|
||||
final BackendAuthServices? backend;
|
||||
final AnalyticsService? analyticsService;
|
||||
final IdentityService? identityService;
|
||||
final LimitsService? limitsService;
|
||||
@@ -56,7 +63,8 @@ class _SxchngeAppState extends State<SxchngeApp> {
|
||||
late final _locale = widget.localeProvider ?? LocaleProvider();
|
||||
|
||||
late final _analytics = widget.analyticsService ?? DummyAnalyticsService();
|
||||
late final _factors = widget.twoFactorService ?? DummyTwoFactorService();
|
||||
late final _factors =
|
||||
widget.backend ?? widget.twoFactorService ?? DummyTwoFactorService();
|
||||
late final _security = SecurityProvider(_factors, _analytics);
|
||||
late final _identity = widget.identityService ?? DummyIdentityService();
|
||||
late final _walletService =
|
||||
@@ -79,12 +87,18 @@ class _SxchngeAppState extends State<SxchngeApp> {
|
||||
late final _journeys = JourneyRecorder(_analytics);
|
||||
late final _services = InstrumentedAuthServices(
|
||||
recorder: _journeys,
|
||||
loginService: GuardedLoginService(widget.loginService, _factors),
|
||||
signupService: widget.signupService,
|
||||
verificationService: widget.verificationService,
|
||||
loginService: GuardedLoginService(
|
||||
widget.backend ?? widget.loginService,
|
||||
_factors,
|
||||
),
|
||||
signupService: widget.backend ?? widget.signupService,
|
||||
verificationService: widget.backend ?? widget.verificationService,
|
||||
);
|
||||
late final _router = createAppRouter()
|
||||
..routeInformationProvider.addListener(_routeChanged);
|
||||
late final _router = createAppRouter(
|
||||
authenticated: widget.backend == null
|
||||
? null
|
||||
: () => widget.backend!.session != null,
|
||||
)..routeInformationProvider.addListener(_routeChanged);
|
||||
String _previousPath = '/';
|
||||
|
||||
@override
|
||||
@@ -109,6 +123,7 @@ class _SxchngeAppState extends State<SxchngeApp> {
|
||||
_wallet.dispose();
|
||||
_security.dispose();
|
||||
_locale.dispose();
|
||||
widget.backend?.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,23 @@ import '../workspace/shell.dart';
|
||||
import '../workspace/transfer.dart';
|
||||
import 'routes.dart';
|
||||
|
||||
GoRouter createAppRouter() {
|
||||
GoRouter createAppRouter({bool Function()? authenticated}) {
|
||||
final topUpKey = GlobalKey<TransferPageState>();
|
||||
final exchangeKey = GlobalKey<TransferPageState>();
|
||||
final payoutKey = GlobalKey<TransferPageState>();
|
||||
final identityKey = GlobalKey<IdentityPageState>();
|
||||
return GoRouter(
|
||||
redirect: (context, state) {
|
||||
if (authenticated == null || authenticated()) return null;
|
||||
final path = state.uri.path;
|
||||
if (path == '/' ||
|
||||
path == '/verify-email' ||
|
||||
path == '/verification-sent' ||
|
||||
path == '/two-factor') {
|
||||
return null;
|
||||
}
|
||||
return '/';
|
||||
},
|
||||
// Signup email is an in-memory navigation extra, never browser-history data.
|
||||
extraCodec: const _TransientExtras(),
|
||||
routes: [
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:sxchnge_auth/sxchnge_auth.dart';
|
||||
|
||||
import 'package:sxchnge_app/main.dart';
|
||||
@@ -21,7 +23,63 @@ class ControlledLogin implements LoginService {
|
||||
}
|
||||
}
|
||||
|
||||
class UnverifiedBackend extends BackendAuthServices {
|
||||
UnverifiedBackend()
|
||||
: super(baseUri: Uri.parse('https://api.example/api/v1'), clientId: 'web');
|
||||
int sends = 0;
|
||||
@override
|
||||
Future<AuthResult> login(LoginRequest request) async =>
|
||||
throw const AuthException(AuthError.accountNotVerified);
|
||||
@override
|
||||
Future<VerificationResult> send({
|
||||
required String email,
|
||||
required String locale,
|
||||
}) async {
|
||||
sends++;
|
||||
return const VerificationResult(isSimulated: false);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('backend mode gates workspace and recovers unverified login', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(1440, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final backend = UnverifiedBackend();
|
||||
await tester.pumpWidget(SxchngeApp(backend: backend));
|
||||
await tester.pumpAndSettle();
|
||||
final router = GoRouter.of(
|
||||
tester.element(find.byKey(const ValueKey('email'))),
|
||||
);
|
||||
router.go('/accounts');
|
||||
await tester.pumpAndSettle();
|
||||
expect(router.routeInformationProvider.value.uri.path, '/');
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('email')),
|
||||
'user@example.com',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('password')),
|
||||
'password-value',
|
||||
);
|
||||
await tester.ensureVisible(find.byKey(const ValueKey('submit')));
|
||||
await tester.tap(find.byKey(const ValueKey('submit')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
router.routeInformationProvider.value.uri.path,
|
||||
'/verification-sent',
|
||||
);
|
||||
expect(backend.sends, 1);
|
||||
expect(
|
||||
find.textContaining('a verification email is on its way'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'app shows loading, localized failure, and retries through injected service',
|
||||
(tester) async {
|
||||
|
||||
@@ -131,7 +131,10 @@ void main() {
|
||||
);
|
||||
await tap(tester, find.byKey(const ValueKey('submit')));
|
||||
expect(find.text('Check your inbox.'), findsOneWidget);
|
||||
expect(find.textContaining('We’ve sent you'), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining('a verification email is on its way'),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tap(tester, find.byKey(const ValueKey('demo-confirmation')));
|
||||
expect(find.text('Your email\nis verified.'), findsOneWidget);
|
||||
expect(sink.events.map((e) => e.journeyId).toSet().length, 1);
|
||||
|
||||
Reference in new issue
Block a user