chore: update i18n (#7846)

* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: only show one copied toast

* chore: add upgrade dialog in invite link

* chore: update dialog style

* fix: flutter tests
This commit is contained in:
Lucas 2025-04-28 20:47:08 +08:00 committed by GitHub
parent 168b29abe3
commit 807aa74509
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 228 additions and 115 deletions

View File

@ -39,7 +39,7 @@ void main() {
);
await tester.tapButton(find.byType(AccountSignInOutButton));
tester.expectToSeeText(LocaleKeys.button_ok.tr());
tester.expectToSeeText(LocaleKeys.button_yes.tr());
await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
// Go to the sign in page again

View File

@ -28,8 +28,8 @@ extension AppFlowyAuthTest on WidgetTester {
await tapButton(find.byType(AccountSignInOutButton));
expectToSeeText(LocaleKeys.button_ok.tr());
await tapButtonWithName(LocaleKeys.button_ok.tr());
expectToSeeText(LocaleKeys.button_yes.tr());
await tapButtonWithName(LocaleKeys.button_yes.tr());
}
Future<void> tapSignInAsGuest() async {

View File

@ -121,41 +121,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
);
}
// Widget _buildInviteMemberArea(BuildContext context) {
// return Column(
// children: [
// TextFormField(
// autofocus: true,
// controller: emailController,
// keyboardType: TextInputType.text,
// decoration: InputDecoration(
// hintText: LocaleKeys.settings_appearance_members_inviteHint.tr(),
// ),
// ),
// const VSpace(16),
// if (exceededLimit) ...[
// FlowyText.regular(
// LocaleKeys.settings_appearance_members_inviteFailedMemberLimitMobile
// .tr(),
// fontSize: 14.0,
// maxLines: 3,
// color: Theme.of(context).colorScheme.error,
// ),
// const VSpace(16),
// ],
// SizedBox(
// width: double.infinity,
// child: ElevatedButton(
// onPressed: () => _inviteMember(context),
// child: Text(
// LocaleKeys.settings_appearance_members_sendInvite.tr(),
// ),
// ),
// ),
// ],
// );
// }
Widget _buildError(BuildContext context) {
return Center(
child: Padding(
@ -194,9 +159,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
final actionType = actionResult.actionType;
final result = actionResult.result;
// get keyboard height
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
// only show the result dialog when the action is WorkspaceMemberActionType.add
if (actionType == WorkspaceMemberActionType.addByEmail) {
result.fold(
@ -204,7 +166,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
message:
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -219,7 +180,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
});
showToastNotification(
type: ToastificationType.error,
bottomPadding: keyboardHeight,
message: message,
);
},
@ -230,7 +190,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
message:
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -247,7 +206,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
type: ToastificationType.error,
message: message,
bottomPadding: keyboardHeight,
);
},
);
@ -258,7 +216,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceSuccess
.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -267,48 +224,65 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceFailed
.tr(),
bottomPadding: keyboardHeight,
);
},
);
} else if (actionType == WorkspaceMemberActionType.generateInviteLink) {
result.fold(
(s) {
(s) async {
showToastNotification(
message: 'Invite link generated successfully',
message: LocaleKeys
.settings_appearance_members_generatedLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
getIt<ClipboardService>().setPlainText(inviteLink);
await getIt<ClipboardService>().setPlainText(inviteLink);
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
message: 'Failed to generate invite link',
type: ToastificationType.error,
message:
LocaleKeys.settings_appearance_members_generatedLinkFailed.tr(),
);
},
);
} else if (actionType == WorkspaceMemberActionType.resetInviteLink) {
result.fold(
(s) async {
showToastNotification(
message: LocaleKeys
.settings_appearance_members_resetLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
await getIt<ClipboardService>().setPlainText(inviteLink);
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
type: ToastificationType.error,
message:
LocaleKeys.settings_appearance_members_resetLinkFailed.tr(),
);
},
);
}
}
// void _inviteMember(BuildContext context) {
// final email = emailController.text;
// if (!isEmail(email)) {
// showToastNotification(
// type: ToastificationType.error,
// message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
// );
// return;
// }
// context
// .read<WorkspaceMemberBloc>()
// .add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email));
// // clear the email field after inviting
// emailController.clear();
// }
}
class _LeaveWorkspaceButton extends StatelessWidget {

View File

@ -179,9 +179,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
final actionType = actionResult.actionType;
final result = actionResult.result;
// get keyboard height
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
// only show the result dialog when the action is WorkspaceMemberActionType.add
if (actionType == WorkspaceMemberActionType.addByEmail) {
result.fold(
@ -189,7 +186,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
message:
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -204,7 +200,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
});
showToastNotification(
type: ToastificationType.error,
bottomPadding: keyboardHeight,
message: message,
);
},
@ -215,7 +210,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
message:
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -229,10 +223,10 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
setState(() {
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
});
showToastNotification(
type: ToastificationType.error,
message: message,
bottomPadding: keyboardHeight,
);
},
);
@ -243,7 +237,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceSuccess
.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -252,7 +245,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceFailed
.tr(),
bottomPadding: keyboardHeight,
);
},
);
@ -260,20 +252,53 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
result.fold(
(s) {
showToastNotification(
message: 'Invite link generated successfully',
message: LocaleKeys
.settings_appearance_members_generatedLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
getIt<ClipboardService>().setPlainText(inviteLink);
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
type: ToastificationType.error,
message: 'Failed to generate invite link',
message:
LocaleKeys.settings_appearance_members_generatedLinkFailed.tr(),
);
},
);
} else if (actionType == WorkspaceMemberActionType.resetInviteLink) {
result.fold(
(s) {
showToastNotification(
message: LocaleKeys
.settings_appearance_members_resetLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
getIt<ClipboardService>().setPlainText(inviteLink);
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
type: ToastificationType.error,
message:
LocaleKeys.settings_appearance_members_resetLinkFailed.tr(),
);
},
);

View File

@ -130,7 +130,7 @@ class _ContinueWithMagicLinkOrPasscodePageState
isSubmitting
? const VerifyingButton()
: ContinueWithButton(
text: LocaleKeys.signIn_continueToSignIn.tr(),
text: LocaleKeys.signIn_continueWithLoginCode.tr(),
onTap: () {
final passcode = passcodeController.text;
if (passcode.isEmpty) {

View File

@ -76,10 +76,11 @@ class AccountSignInOutButton extends StatelessWidget {
}
void _showLogoutDialog(BuildContext context) {
showConfirmDialog(
showCancelAndConfirmDialog(
context: context,
title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
description: LocaleKeys.settings_menu_logoutPrompt.tr(),
confirmLabel: LocaleKeys.button_yes.tr(),
onConfirm: () async {
await getIt<AuthService>().signOut();
onAction();

View File

@ -85,7 +85,28 @@ class _Description extends StatelessWidget {
}
Future<void> _onGenerateInviteLink(BuildContext context) async {
final inviteLink = context.read<WorkspaceMemberBloc>().state.inviteLink;
final state = context.read<WorkspaceMemberBloc>().state;
final inviteLink = state.inviteLink;
// check the current workspace member count, if it exceed the limit, show a upgrade dialog.
// prevent hard code here, because the member count may exceed the limit after the invite link is generated.
if (inviteLink == null && state.members.length >= 3) {
await showConfirmDialog(
context: context,
title:
LocaleKeys.settings_appearance_members_inviteFailedDialogTitle.tr(),
description:
LocaleKeys.settings_appearance_members_inviteFailedMemberLimit.tr(),
confirmLabel: LocaleKeys
.settings_appearance_members_memberLimitExceededUpgrade
.tr(),
onConfirm: () => context
.read<WorkspaceMemberBloc>()
.add(const WorkspaceMemberEvent.upgradePlan()),
);
return;
}
if (inviteLink != null) {
// show a dialog to confirm if the user wants to copy the link to the clipboard
await showConfirmDialog(
@ -120,9 +141,16 @@ class _Description extends StatelessWidget {
}
}
class _CopyLinkButton extends StatelessWidget {
class _CopyLinkButton extends StatefulWidget {
const _CopyLinkButton();
@override
State<_CopyLinkButton> createState() => _CopyLinkButtonState();
}
class _CopyLinkButtonState extends State<_CopyLinkButton> {
ToastificationItem? toastificationItem;
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
@ -135,16 +163,42 @@ class _CopyLinkButton extends StatelessWidget {
horizontal: theme.spacing.l,
vertical: theme.spacing.s,
),
onTap: () {
final link = context.read<WorkspaceMemberBloc>().state.inviteLink;
onTap: () async {
final state = context.read<WorkspaceMemberBloc>().state;
// check the current workspace member count, if it exceed the limit, show a upgrade dialog.
// prevent hard code here, because the member count may exceed the limit after the invite link is generated.
if (state.members.length >= 3) {
await showConfirmDialog(
context: context,
title: LocaleKeys
.settings_appearance_members_inviteFailedDialogTitle
.tr(),
description: LocaleKeys
.settings_appearance_members_inviteFailedMemberLimit
.tr(),
confirmLabel: LocaleKeys
.settings_appearance_members_memberLimitExceededUpgrade
.tr(),
onConfirm: () => context
.read<WorkspaceMemberBloc>()
.add(const WorkspaceMemberEvent.upgradePlan()),
);
return;
}
final link = state.inviteLink;
if (link != null) {
getIt<ClipboardService>().setData(
await getIt<ClipboardService>().setData(
ClipboardServiceData(
plainText: link,
),
);
showToastNotification(
if (toastificationItem != null) {
toastification.dismiss(toastificationItem!);
}
toastificationItem = showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
} else {

View File

@ -239,13 +239,17 @@ class WorkspaceMemberBloc
String link,
) async {}
Future<void> _onGenerateInviteLink(Emitter<WorkspaceMemberState> emit) async {
Future<void> _onGenerateInviteLink(
Emitter<WorkspaceMemberState> emit,
) async {
final workspaceId = _workspaceId.value;
if (workspaceId == null) {
Log.error('Failed to generate invite link: workspaceId is null');
return;
}
final resetInviteLink = state.inviteLink != null;
final result = await _memberHttpService?.generateInviteCode(
workspaceId: workspaceId,
);
@ -257,7 +261,9 @@ class WorkspaceMemberBloc
state.copyWith(
inviteLink: inviteLink,
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.generateInviteLink,
actionType: resetInviteLink
? WorkspaceMemberActionType.resetInviteLink
: WorkspaceMemberActionType.generateInviteLink,
result: result,
),
),
@ -268,7 +274,9 @@ class WorkspaceMemberBloc
emit(
state.copyWith(
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.generateInviteLink,
actionType: resetInviteLink
? WorkspaceMemberActionType.resetInviteLink
: WorkspaceMemberActionType.generateInviteLink,
result: result,
),
),
@ -359,7 +367,9 @@ class WorkspaceMemberBloc
_memberHttpService?.getInviteCode(workspaceId: workspaceId).fold(
(s) async {
final inviteLink = await _buildInviteLink(inviteCode: s);
add(WorkspaceMemberEvent.updateInviteLink(inviteLink));
if (!isClosed) {
add(WorkspaceMemberEvent.updateInviteLink(inviteLink));
}
},
(e) => Log.info('Failed to get invite code: ${e.msg}', e),
),
@ -472,6 +482,7 @@ enum WorkspaceMemberActionType {
inviteByEmail,
inviteByLink,
generateInviteLink,
resetInviteLink,
// this event will add the member without sending an invitation
addByEmail,
removeByEmail,

View File

@ -252,27 +252,70 @@ class WorkspaceMembersPage extends StatelessWidget {
.settings_appearance_members_inviteFailedDialogTitle
.tr(),
description: message,
confirmLabel: LocaleKeys.button_ok.tr(),
confirmLabel: LocaleKeys
.settings_appearance_members_memberLimitExceededUpgrade
.tr(),
onConfirm: () => context
.read<WorkspaceMemberBloc>()
.add(const WorkspaceMemberEvent.upgradePlan()),
);
},
);
} else if (actionType == WorkspaceMemberActionType.generateInviteLink) {
result.fold(
(s) {
(s) async {
showToastNotification(
message: 'Invite link generated successfully',
message: LocaleKeys
.settings_appearance_members_generatedLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
getIt<ClipboardService>().setPlainText(inviteLink);
await getIt<ClipboardService>().setPlainText(inviteLink);
Future.delayed(const Duration(milliseconds: 200), () {
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
});
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
message: 'Failed to generate invite link',
type: ToastificationType.error,
message:
LocaleKeys.settings_appearance_members_generatedLinkFailed.tr(),
);
},
);
} else if (actionType == WorkspaceMemberActionType.resetInviteLink) {
result.fold(
(s) async {
showToastNotification(
message: LocaleKeys
.settings_appearance_members_resetLinkSuccessfully
.tr(),
);
// copy the invite link to the clipboard
final inviteLink = state.inviteLink;
if (inviteLink != null) {
await getIt<ClipboardService>().setPlainText(inviteLink);
Future.delayed(const Duration(milliseconds: 200), () {
showToastNotification(
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
});
}
},
(f) {
Log.error('generate invite link failed: $f');
showToastNotification(
type: ToastificationType.error,
message:
LocaleKeys.settings_appearance_members_resetLinkFailed.tr(),
);
},
);
@ -391,6 +434,8 @@ class _MemberItem extends StatelessWidget {
style: theme.textStyle.body.enhanced(
color: theme.textColorScheme.primary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
_formatJoinedDate(member.joinedAt.toInt()),
@ -481,20 +526,18 @@ class _MemberMoreActionList extends StatelessWidget {
onSelected: (action, controller) {
switch (action.inner) {
case _MemberMoreAction.delete:
showDialog(
showCancelAndConfirmDialog(
context: context,
builder: (_) => NavigatorOkCancelDialog(
title: LocaleKeys.settings_appearance_members_removeMember.tr(),
message: LocaleKeys
.settings_appearance_members_areYouSureToRemoveMember
.tr(),
onOkPressed: () => context.read<WorkspaceMemberBloc>().add(
WorkspaceMemberEvent.removeWorkspaceMemberByEmail(
action.member.email,
),
title: LocaleKeys.settings_appearance_members_removeMember.tr(),
description: LocaleKeys
.settings_appearance_members_areYouSureToRemoveMember
.tr(),
confirmLabel: LocaleKeys.button_yes.tr(),
onConfirm: () => context.read<WorkspaceMemberBloc>().add(
WorkspaceMemberEvent.removeWorkspaceMemberByEmail(
action.member.email,
),
okTitle: LocaleKeys.button_yes.tr(),
),
),
);
break;
}

View File

@ -77,6 +77,7 @@
"temporaryVerificationLinkSent": "A temporary verification link has been sent.\nPlease check your inbox at",
"temporaryVerificationCodeSent": "A temporary verification code has been sent.\nPlease check your inbox at",
"continueToSignIn": "Continue to sign in",
"continueWithLoginCode": "Continue with login code",
"backToLogin": "Back to login",
"enterCode": "Enter code",
"enterCodeManually": "Enter code manually",
@ -563,7 +564,7 @@
"description": "This change will apply to all the published pages live on this namespace",
"tooltip": "We reserve the rights to remove any inappropriate namespaces",
"updateExistingNamespace": "Update existing namespace",
"upgradeToPro": "Upgrade to Pro Plan to set a homepage",
"upgradeToPro": "Upgrade to Pro Plan to claim a custom namespace",
"redirectToPayment": "Redirecting to payment page...",
"onlyWorkspaceOwnerCanSetHomePage": "Only the workspace owner can set a homepage",
"pleaseAskOwnerToSetHomePage": "Please ask the workspace owner to upgrade to Pro Plan"
@ -1156,8 +1157,8 @@
"files": "Files",
"notifications": "Notifications",
"open": "Open Settings",
"logout": "Logout",
"logoutPrompt": "Are you sure you want to logout?",
"logout": "Log out",
"logoutPrompt": "Are you sure you want to log out?",
"selfEncryptionLogoutPrompt": "Are you sure you want to log out? Please ensure you have copied the encryption secret",
"syncSetting": "Sync Setting",
"cloudSettings": "Cloud Settings",
@ -1353,8 +1354,8 @@
"one": "{} member",
"other": "{} members"
},
"inviteFailedDialogTitle": "Failed to send invite",
"inviteFailedMemberLimit": "Member limit has been reached, please upgrade to invite more members.",
"inviteFailedDialogTitle": "Upgrade to Pro Plan",
"inviteFailedMemberLimit": "This workspace has reached the free limit. Please upgrade to Pro to unlock more members.",
"inviteFailedMemberLimitMobile": "Your workspace has reached the member limit.",
"memberLimitExceeded": "Member limit reached, to invite more members, please ",
"memberLimitExceededUpgrade": "upgrade",
@ -1375,7 +1376,7 @@
"inviteMemberByEmail": "Invite member by email",
"inviteMemberHintText": "Invite by email",
"resetInviteLink": "Reset the invite link?",
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer be available.",
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer valid.",
"adminPanel": "Admin Panel",
"reset": "Reset",
"resetInviteLinkSuccess": "Invite link reset successfully",
@ -1384,7 +1385,11 @@
"memberPageDescription1": "Access the",
"memberPageDescription2": "for guest and advanced user management.",
"noInviteLink": "You haven't generated an invite link yet.",
"copyLink": "Copy link"
"copyLink": "Copy link",
"generatedLinkSuccessfully": "Generated link successfully",
"generatedLinkFailed": "Failed to generate link",
"resetLinkSuccessfully": "Reset link successfully",
"resetLinkFailed": "Failed to reset link"
}
},
"files": {
@ -1433,7 +1438,7 @@
"tooltipSelectIcon": "Select icon",
"selectAnIcon": "Select an icon",
"pleaseInputYourOpenAIKey": "please input your AI key",
"clickToLogout": "Click to logout the current user"
"clickToLogout": "Click to log out the current user"
},
"mobile": {
"personalInfo": "Personal Information",