From 34c441f3ad878aeb8e1f234d53833813c99a6945 Mon Sep 17 00:00:00 2001 From: koukemo <64585615+koukemo@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:28:09 +0900 Subject: [PATCH 01/60] fix: add libnotify and rocksdb as docker dependencies (#6065) --- frontend/scripts/docker-buildfiles/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/scripts/docker-buildfiles/Dockerfile b/frontend/scripts/docker-buildfiles/Dockerfile index a2e15cfd1b41a..9c58568a66823 100644 --- a/frontend/scripts/docker-buildfiles/Dockerfile +++ b/frontend/scripts/docker-buildfiles/Dockerfile @@ -73,7 +73,7 @@ FROM archlinux/archlinux RUN pacman -Syyu --noconfirm # Install runtime dependencies -RUN pacman -S --noconfirm xdg-user-dirs gtk3 libkeybinder3 && \ +RUN pacman -S --noconfirm xdg-user-dirs gtk3 libkeybinder3 libnotify rocksdb && \ pacman -Scc --noconfirm # Set up appflowy user From 29858dda7a9502d2db3e75fbe6b4ecae25b5f622 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:35:57 +0800 Subject: [PATCH 02/60] chore: update plan desc (#6120) --- .../settings_plan_comparison_dialog.dart | 9 +++++++ frontend/resources/translations/en.json | 25 +++++++++++-------- .../rust-lib/flowy-ai/src/local_ai/watch.rs | 4 --- .../rust-lib/flowy-database2/src/manager.rs | 4 ++- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index f2dec9550c7f3..3ce6c6aca2646 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -668,6 +668,9 @@ final _planLabels = [ label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSix.tr(), tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipSix.tr(), ), + _PlanItem( + label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFileUpload.tr(), + ), ]; class _CellItem { @@ -703,6 +706,9 @@ final List<_CellItem> _freeLabels = [ _CellItem( label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemSix.tr(), ), + _CellItem( + label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemFileUpload.tr(), + ), ]; final List<_CellItem> _proLabels = [ @@ -731,4 +737,7 @@ final List<_CellItem> _proLabels = [ _CellItem( label: LocaleKeys.settings_comparePlanDialog_proLabels_itemSix.tr(), ), + _CellItem( + label: LocaleKeys.settings_comparePlanDialog_proLabels_itemFileUpload.tr(), + ), ]; diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index b2270cfd5d0fd..5129ba26c1b98 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -747,14 +747,14 @@ "title": "AI Max", "description": "Unlimited AI responses powered by GPT-4o, Claude 3.5 Sonnet, and more", "price": "{}", - "priceInfo": "per user per month billed annually", + "priceInfo": "Per user per month billed annually", "recommend": "" }, "aiOnDevice": { "title": "AI On-device for Mac", "description": "Run Mistral 7B, LLAMA 3, and more local models on your machine", "price": "{}", - "priceInfo": "per user per month billed annually", + "priceInfo": "Per user per month billed annually", "recommend": "Recommend M1 or newer" } }, @@ -825,13 +825,13 @@ "title": "Free", "description": "For individuals up to 2 members to organize everything", "price": "{}", - "priceInfo": "free forever" + "priceInfo": "Free forever" }, "proPlan": { "title": "Pro", "description": "For small teams to manage projects and team knowledge", "price": "{}", - "priceInfo": "per user per month \nbilled annually\n\n{} billed monthly" + "priceInfo": "Per user per month \nbilled annually\n\n{} billed monthly" }, "planLabels": { "itemOne": "Workspaces", @@ -840,26 +840,29 @@ "itemFour": "Real-time collaboration", "itemFive": "Mobile app", "itemSix": "AI Responses", + "itemFileUpload": "File uploads", "tooltipSix": "Lifetime means the number of responses never reset", "intelligentSearch": "Intelligent search", "tooltipSeven": "Allows you to customize part of the URL for your workspace" }, "freeLabels": { - "itemOne": "charged per workspace", - "itemTwo": "up to 2", + "itemOne": "Charged per workspace", + "itemTwo": "Up to 2", "itemThree": "5 GB", "itemFour": "yes", "itemFive": "yes", "itemSix": "100 lifetime", + "itemFileUpload": "Up to 7 MB", "intelligentSearch": "Intelligent search" }, "proLabels": { - "itemOne": "charged per workspace", - "itemTwo": "up to 10", - "itemThree": "unlimited", + "itemOne": "Charged per workspace", + "itemTwo": "Up to 10", + "itemThree": "Unlimited", "itemFour": "yes", "itemFive": "yes", - "itemSix": "unlimited", + "itemSix": "Unlimited", + "itemFileUpload": "Unlimited", "intelligentSearch": "Intelligent search" }, "paymentSuccess": { @@ -2502,4 +2505,4 @@ "uploadFailedDescription": "The file upload failed", "uploadingDescription": "The file is being uploaded" } -} +} \ No newline at end of file diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/watch.rs b/frontend/rust-lib/flowy-ai/src/local_ai/watch.rs index 974484f92293e..93c332de15bdd 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/watch.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/watch.rs @@ -16,10 +16,6 @@ pub fn watch_offline_app() -> FlowyResult<(WatchContext, UnboundedReceiver| match res { diff --git a/frontend/rust-lib/flowy-database2/src/manager.rs b/frontend/rust-lib/flowy-database2/src/manager.rs index 7f09840a445fa..344a1516d78dc 100644 --- a/frontend/rust-lib/flowy-database2/src/manager.rs +++ b/frontend/rust-lib/flowy-database2/src/manager.rs @@ -815,7 +815,9 @@ impl DatabaseCollabService for WorkspaceDatabaseCollabServiceImpl { } }, Err(err) => { - error!("build collab: failed to get encode collab: {}", err); + if !matches!(err, DatabaseError::ActionCancelled) { + error!("build collab: failed to get encode collab: {}", err); + } return Err(err); }, } From 61ad75502f17c780b0c5e6e9af4347b965a99baf Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 30 Aug 2024 16:10:17 +0800 Subject: [PATCH 03/60] feat: upgrade template button style (#6121) --- .../menu/sidebar/footer/sidebar_footer.dart | 33 +++++++++++++------ .../sidebar/footer/sidebar_footer_button.dart | 12 ++++--- .../resources/flowy_icons/16x/icon_delete.svg | 14 ++++++++ .../flowy_icons/16x/icon_template.svg | 8 ++--- 4 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 frontend/resources/flowy_icons/16x/icon_delete.svg diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart index fa86082e74862..ff33a79683381 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart @@ -9,6 +9,7 @@ import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; import 'sidebar_footer_button.dart'; @@ -26,11 +27,26 @@ class SidebarFooter extends StatelessWidget { return const SidebarToast(); }, ), - const SidebarTemplateButton(), - const SidebarTrashButton(), + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded(child: SidebarTemplateButton()), + _buildVerticalDivider(context), + const Expanded(child: SidebarTrashButton()), + ], + ), ], ); } + + Widget _buildVerticalDivider(BuildContext context) { + return Container( + width: 1.0, + height: 14, + margin: const EdgeInsets.symmetric(horizontal: 4), + color: AFThemeExtension.of(context).borderColor, + ); + } } class SidebarTemplateButton extends StatelessWidget { @@ -39,12 +55,9 @@ class SidebarTemplateButton extends StatelessWidget { @override Widget build(BuildContext context) { return SidebarFooterButton( - leftIconSize: const Size.square(24.0), - leftIcon: const Padding( - padding: EdgeInsets.all(2.0), - child: FlowySvg( - FlowySvgs.icon_template_s, - ), + leftIconSize: const Size.square(18.0), + leftIcon: const FlowySvg( + FlowySvgs.icon_template_s, ), text: LocaleKeys.template_label.tr(), onTap: () => afLaunchUrlString('https://appflowy.io/templates'), @@ -61,9 +74,9 @@ class SidebarTrashButton extends StatelessWidget { valueListenable: getIt().notifier, builder: (context, value, child) { return SidebarFooterButton( - leftIconSize: const Size.square(24.0), + leftIconSize: const Size.square(18.0), leftIcon: const FlowySvg( - FlowySvgs.sidebar_footer_trash_m, + FlowySvgs.icon_delete_s, ), text: LocaleKeys.trash_text.tr(), onTap: () { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer_button.dart index f83e1dd046365..cbb969d191d11 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer_button.dart @@ -26,11 +26,15 @@ class SidebarFooterButton extends StatelessWidget { child: FlowyButton( leftIcon: leftIcon, leftIconSize: leftIconSize, - iconPadding: 8.0, margin: const EdgeInsets.all(4.0), - text: FlowyText.regular( - text, - lineHeight: 1.15, + expandText: false, + text: Padding( + padding: const EdgeInsets.only(right: 6.0), + child: FlowyText( + text, + fontWeight: FontWeight.w400, + figmaLineHeight: 18.0, + ), ), onTap: onTap, ), diff --git a/frontend/resources/flowy_icons/16x/icon_delete.svg b/frontend/resources/flowy_icons/16x/icon_delete.svg new file mode 100644 index 0000000000000..de7c3a7fc1cdf --- /dev/null +++ b/frontend/resources/flowy_icons/16x/icon_delete.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/icon_template.svg b/frontend/resources/flowy_icons/16x/icon_template.svg index 1b6af1bac6ae9..84a83c63f3e36 100644 --- a/frontend/resources/flowy_icons/16x/icon_template.svg +++ b/frontend/resources/flowy_icons/16x/icon_template.svg @@ -1,5 +1,5 @@ - - - + + + - \ No newline at end of file + From 0fd0900b41617cc721179de4ef959abbd919d008 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:12:16 +0200 Subject: [PATCH 04/60] fix: add leading zeros to day & month in date format (#6114) --- .../settings/date_time/date_format_ext.dart | 10 +++++----- .../settings/pages/settings_workspace_view.dart | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart index 863482cca8756..8d2a65c0298bc 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart @@ -1,11 +1,11 @@ import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; -const _localFmt = 'M/d/y'; -const _usFmt = 'y/M/d'; -const _isoFmt = 'y-M-d'; -const _friendlyFmt = 'MMM d, y'; -const _dmyFmt = 'd/M/y'; +const _localFmt = 'MM/dd/y'; +const _usFmt = 'y/MM/dd'; +const _isoFmt = 'y-MM-dd'; +const _friendlyFmt = 'MMM dd, y'; +const _dmyFmt = 'dd/MM/y'; extension DateFormatter on UserDateFormatPB { DateFormat get toFormat => DateFormat(_toFormat[this] ?? _friendlyFmt); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart index abe56bf611cd7..7758a375b76f7 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; @@ -43,7 +45,6 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; From 47c2ae23ed4996f1043c65e6fa90bc17db4676ca Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:12:26 +0200 Subject: [PATCH 05/60] fix: move title bar on top of tabs on Windows (#6116) --- .../lib/core/frameless_window.dart | 71 +------------------ .../presentation/home/home_stack.dart | 68 ++++++++++++++++-- 2 files changed, 65 insertions(+), 74 deletions(-) diff --git a/frontend/appflowy_flutter/lib/core/frameless_window.dart b/frontend/appflowy_flutter/lib/core/frameless_window.dart index ea6c730855596..15732f163dfa6 100644 --- a/frontend/appflowy_flutter/lib/core/frameless_window.dart +++ b/frontend/appflowy_flutter/lib/core/frameless_window.dart @@ -1,15 +1,7 @@ import 'dart:io'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/shared/window_title_bar.dart'; -import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; class CocoaWindowChannel { CocoaWindowChannel._(); @@ -38,11 +30,9 @@ class MoveWindowDetector extends StatefulWidget { const MoveWindowDetector({ super.key, this.child, - this.showTitleBar = false, }); final Widget? child; - final bool showTitleBar; @override MoveWindowDetectorState createState() => MoveWindowDetectorState(); @@ -54,28 +44,10 @@ class MoveWindowDetectorState extends State { @override Widget build(BuildContext context) { - if (!Platform.isMacOS && !Platform.isWindows) { + if (!Platform.isMacOS) { return widget.child ?? const SizedBox.shrink(); } - if (Platform.isWindows) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showTitleBar) ...[ - WindowTitleBar( - leftChildren: [ - _buildToggleMenuButton(context), - ], - ), - ] else ...[ - const SizedBox(height: 5), - ], - widget.child ?? const SizedBox.shrink(), - ], - ); - } - return GestureDetector( // https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack behavior: HitTestBehavior.translucent, @@ -96,45 +68,4 @@ class MoveWindowDetectorState extends State { child: widget.child, ); } - - Widget _buildToggleMenuButton(BuildContext context) { - if (!context.read().state.isMenuCollapsed) { - return const SizedBox.shrink(); - } - - final textSpan = TextSpan( - children: [ - TextSpan( - text: '${LocaleKeys.sideBar_openSidebar.tr()}\n', - style: context.tooltipTextStyle(), - ), - TextSpan( - text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\', - style: context - .tooltipTextStyle() - ?.copyWith(color: Theme.of(context).hintColor), - ), - ], - ); - - return FlowyTooltip( - richMessage: textSpan, - child: Listener( - behavior: HitTestBehavior.translucent, - onPointerDown: (_) => context - .read() - .add(const HomeSettingEvent.collapseMenu()), - child: FlowyHover( - child: Container( - width: 24, - padding: const EdgeInsets.all(4), - child: const RotatedBox( - quarterTurns: 2, - child: FlowySvg(FlowySvgs.hide_menu_s), - ), - ), - ), - ), - ); - } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart index 405360f55b7a9..d1e1b2c221fdd 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart @@ -1,8 +1,16 @@ -import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + import 'package:appflowy/core/frameless_window.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/blank/blank.dart'; +import 'package:appflowy/shared/window_title_bar.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/navigation.dart'; @@ -10,9 +18,10 @@ import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:time/time.dart'; @@ -47,6 +56,17 @@ class HomeStack extends StatelessWidget { builder: (context, state) { return Column( children: [ + if (Platform.isWindows) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + WindowTitleBar( + leftChildren: [ + _buildToggleMenuButton(context), + ], + ), + ], + ), Padding( padding: EdgeInsets.only(left: layout.menuSpacing), child: TabsManager(pageController: pageController), @@ -73,6 +93,47 @@ class HomeStack extends StatelessWidget { ), ); } + + Widget _buildToggleMenuButton(BuildContext context) { + if (!context.read().state.isMenuCollapsed) { + return const SizedBox.shrink(); + } + + final textSpan = TextSpan( + children: [ + TextSpan( + text: '${LocaleKeys.sideBar_openSidebar.tr()}\n', + style: context.tooltipTextStyle(), + ), + TextSpan( + text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\', + style: context + .tooltipTextStyle() + ?.copyWith(color: Theme.of(context).hintColor), + ), + ], + ); + + return FlowyTooltip( + richMessage: textSpan, + child: Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: (_) => context + .read() + .add(const HomeSettingEvent.collapseMenu()), + child: FlowyHover( + child: Container( + width: 24, + padding: const EdgeInsets.all(4), + child: const RotatedBox( + quarterTurns: 2, + child: FlowySvg(FlowySvgs.hide_menu_s), + ), + ), + ), + ), + ); + } } class PageStack extends StatefulWidget { @@ -230,7 +291,6 @@ class PageManager { child: Selector( selector: (context, notifier) => notifier.titleWidget, builder: (_, __, child) => MoveWindowDetector( - showTitleBar: true, child: HomeTopBar(layout: layout), ), ), From 78c2e756d6db046da1b4f0dbceccd6ebac225644 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:12:36 +0200 Subject: [PATCH 06/60] fix: lose focus when changing tabs (#6118) --- .../lib/workspace/presentation/home/tabs/tabs_manager.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/tabs_manager.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/tabs_manager.dart index 07870a152d7f3..51f3e0b766cac 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/tabs_manager.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/tabs_manager.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -74,8 +75,10 @@ class _TabsManagerState extends State dividerColor: Colors.transparent, isScrollable: true, controller: _controller, - onTap: (newIndex) => - context.read().add(TabsEvent.selectTab(newIndex)), + onTap: (newIndex) { + AFFocusManager.of(context).notifyLoseFocus(); + context.read().add(TabsEvent.selectTab(newIndex)); + }, tabs: state.pageManagers .map( (pm) => FlowyTab( From 928da2a223f885baa433fb66f0db4272bf182905 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 30 Aug 2024 19:19:41 +0800 Subject: [PATCH 07/60] chore: update translations (#6124) * chore: update translations * fix: close popup menu when tapping navigation bar item * chore: update toolbar divider color and popup menu background color * chore: update translations and icon * chore: update frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> --------- Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> --- .../home/mobile_home_trash_page.dart | 41 +++++++-- .../home/setting/settings_popup_menu.dart | 15 ++-- .../home/shared/empty_placeholder.dart | 5 +- .../mobile_bottom_navigation_bar.dart | 41 ++++++--- .../widgets/settings_popup_menu.dart | 13 +-- .../appflowy_mobile_toolbar.dart | 4 +- .../slash_menu/slash_menu_items.dart | 8 +- .../popup_menu/appflowy_popup_menu.dart | 83 ++++++++++++++----- .../flowy_icons/40x/m_empty_trash.svg | 9 ++ frontend/resources/translations/en.json | 28 +++---- 10 files changed, 178 insertions(+), 69 deletions(-) create mode 100644 frontend/resources/flowy_icons/40x/m_empty_trash.svg diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart index 2ee9e14175685..73a5381d42c2c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart @@ -64,11 +64,7 @@ class MobileHomeTrashPage extends StatelessWidget { ], ), body: state.objects.isEmpty - ? FlowyMobileStateContainer.info( - emoji: '🗑️', - title: LocaleKeys.trash_mobile_empty.tr(), - description: LocaleKeys.trash_mobile_emptyDescription.tr(), - ) + ? const _EmptyTrashBin() : _DeletedFilesListView(state), ); }, @@ -82,6 +78,41 @@ enum _TrashActionType { deleteAll, } +class _EmptyTrashBin extends StatelessWidget { + const _EmptyTrashBin(); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const FlowySvg( + FlowySvgs.m_empty_trash_xl, + size: Size.square(46), + ), + const VSpace(16.0), + FlowyText.medium( + LocaleKeys.trash_mobile_empty.tr(), + fontSize: 18.0, + textAlign: TextAlign.center, + ), + const VSpace(8.0), + FlowyText.regular( + LocaleKeys.trash_mobile_emptyDescription.tr(), + fontSize: 17.0, + maxLines: 10, + textAlign: TextAlign.center, + lineHeight: 1.3, + color: Theme.of(context).hintColor, + ), + const VSpace(kBottomNavigationBarHeight + 36.0), + ], + ), + ); + } +} + class _TrashActionAllButton extends StatelessWidget { /// Switch between 'delete all' and 'restore all' feature const _TrashActionAllButton({ diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart index a13f7b3c75e14..ac7dfa17e6e6f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart @@ -22,6 +22,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget { @override Widget build(BuildContext context) { + return PopupMenuButton<_MobileSettingsPopupMenuItem>( offset: const Offset(0, 36), padding: EdgeInsets.zero, @@ -32,13 +33,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget { ), shadowColor: const Color(0x68000000), elevation: 10, - color: Theme.of(context).colorScheme.surface, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: FlowySvg( - FlowySvgs.m_settings_more_s, - ), - ), + color: context.popupMenuBackgroundColor, itemBuilder: (BuildContext context) => >[ _buildItem( @@ -81,6 +76,12 @@ class HomePageSettingsPopupMenu extends StatelessWidget { break; } }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: FlowySvg( + FlowySvgs.m_settings_more_s, + ), + ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart index 1b99be9a424b8..2de40600f2cc8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart @@ -6,7 +6,10 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; class EmptySpacePlaceholder extends StatelessWidget { - const EmptySpacePlaceholder({super.key, required this.type}); + const EmptySpacePlaceholder({ + super.key, + required this.type, + }); final MobilePageCardType type; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index 1e1cd88ad903d..1de6c568d3067 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -1,9 +1,11 @@ +import 'dart:io'; import 'dart:ui'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/notifications/mobile_notifications_screen.dart'; import 'package:appflowy/mobile/presentation/widgets/navigation_bar_button.dart'; +import 'package:appflowy/shared/popup_menu/appflowy_popup_menu.dart'; import 'package:appflowy/shared/red_dot.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -193,25 +195,44 @@ class _HomePageNavigationBar extends StatelessWidget { border: context.border, color: context.backgroundColor, ), - child: BottomNavigationBar( - showSelectedLabels: false, - showUnselectedLabels: false, - enableFeedback: false, - type: BottomNavigationBarType.fixed, - elevation: 0, - items: _items, - backgroundColor: Colors.transparent, - currentIndex: navigationShell.currentIndex, - onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex), + child: Theme( + data: _getThemeData(context), + child: BottomNavigationBar( + showSelectedLabels: false, + showUnselectedLabels: false, + enableFeedback: false, + type: BottomNavigationBarType.fixed, + elevation: 0, + items: _items, + backgroundColor: Colors.transparent, + currentIndex: navigationShell.currentIndex, + onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex), + ), ), ), ), ); } + ThemeData _getThemeData(BuildContext context) { + if (Platform.isAndroid) { + return Theme.of(context); + } + + // hide the splash effect for iOS + return Theme.of(context).copyWith( + splashFactory: NoSplash.splashFactory, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + ); + } + /// Navigate to the current location of the branch at the provided index when /// tapping an item in the BottomNavigationBar. void _onTap(BuildContext context, int bottomBarIndex) { + // close the popup menu + closePopupMenu(); + final label = _items[bottomBarIndex].label; if (label == _addLabel) { // show an add dialog diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart index b414158aa541d..dfa277f2ef33d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart @@ -36,12 +36,7 @@ class NotificationSettingsPopupMenu extends StatelessWidget { // todo: replace it with shadows shadowColor: const Color(0x68000000), elevation: 10, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: FlowySvg( - FlowySvgs.m_settings_more_s, - ), - ), + color: context.popupMenuBackgroundColor, itemBuilder: (BuildContext context) => >[ _buildItem( @@ -87,6 +82,12 @@ class NotificationSettingsPopupMenu extends StatelessWidget { break; } }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: FlowySvg( + FlowySvgs.m_settings_more_s, + ), + ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index 894dde0442395..6d7d0860ef1a4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -245,12 +245,12 @@ class _MobileToolbarState extends State<_MobileToolbar> children: [ const Divider( height: 0.5, - color: Color(0xFFEDEDED), + color: Color(0x7FEDEDED), ), _buildToolbar(context), const Divider( height: 0.5, - color: Color(0xFFEDEDED), + color: Color(0x7FEDEDED), ), _buildMenuOrSpacer(context), ], diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart index 0c3868a50a25f..09ea9faa859da 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart @@ -264,7 +264,13 @@ final referencedDocSlashMenuItem = SelectionMenuItem( isSelected: isSelected, style: style, ), - keywords: ['page', 'notes', 'referenced page', 'referenced document'], + keywords: [ + 'page', + 'notes', + 'referenced page', + 'referenced document', + 'link to page', + ], handler: (editorState, menuService, context) => showLinkToPageMenu( editorState, menuService, diff --git a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart index e6ace027fa1fb..0c3d759744f0f 100644 --- a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart @@ -33,6 +33,12 @@ const double _kMenuVerticalPadding = 8.0; const double _kMenuWidthStep = 56.0; const double _kMenuScreenPadding = 8.0; +GlobalKey<_PopupMenuState>? _kPopupMenuKey; +void closePopupMenu() { + _kPopupMenuKey?.currentState?.dismiss(); + _kPopupMenuKey = null; +} + /// A base class for entries in a Material Design popup menu. /// /// The popup menu widget uses this interface to interact with the menu items. @@ -569,7 +575,7 @@ class _CheckedPopupMenuItemState } } -class _PopupMenu extends StatelessWidget { +class _PopupMenu extends StatefulWidget { const _PopupMenu({ super.key, required this.itemKeys, @@ -585,10 +591,15 @@ class _PopupMenu extends StatelessWidget { final BoxConstraints? constraints; final Clip clipBehavior; + @override + State<_PopupMenu> createState() => _PopupMenuState(); +} + +class _PopupMenuState extends State<_PopupMenu> { @override Widget build(BuildContext context) { final double unit = 1.0 / - (route.items.length + + (widget.route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. final List children = []; final ThemeData theme = Theme.of(context); @@ -597,16 +608,16 @@ class _PopupMenu extends StatelessWidget { ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context); - for (int i = 0; i < route.items.length; i += 1) { + for (int i = 0; i < widget.route.items.length; i += 1) { final double start = (i + 1) * unit; final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( - parent: route.animation!, + parent: widget.route.animation!, curve: Interval(start, end), ); - Widget item = route.items[i]; - if (route.initialValue != null && - route.items[i].represents(route.initialValue)) { + Widget item = widget.route.items[i]; + if (widget.route.initialValue != null && + widget.route.items[i].represents(widget.route.initialValue)) { item = ColoredBox( color: Theme.of(context).highlightColor, child: item, @@ -615,10 +626,10 @@ class _PopupMenu extends StatelessWidget { children.add( _MenuItem( onLayout: (Size size) { - route.itemSizes[i] = size; + widget.route.itemSizes[i] = size; }, child: FadeTransition( - key: itemKeys[i], + key: widget.itemKeys[i], opacity: opacity, child: item, ), @@ -630,10 +641,10 @@ class _PopupMenu extends StatelessWidget { CurveTween(curve: const Interval(0.0, 1.0 / 3.0)); final CurveTween width = CurveTween(curve: Interval(0.0, unit)); final CurveTween height = - CurveTween(curve: Interval(0.0, unit * route.items.length)); + CurveTween(curve: Interval(0.0, unit * widget.route.items.length)); final Widget child = ConstrainedBox( - constraints: constraints ?? + constraints: widget.constraints ?? const BoxConstraints( minWidth: _kMenuMinWidth, maxWidth: _kMenuMaxWidth, @@ -644,7 +655,7 @@ class _PopupMenu extends StatelessWidget { scopesRoute: true, namesRoute: true, explicitChildNodes: true, - label: semanticLabel, + label: widget.semanticLabel, child: SingleChildScrollView( padding: const EdgeInsets.symmetric( vertical: _kMenuVerticalPadding, @@ -656,28 +667,28 @@ class _PopupMenu extends StatelessWidget { ); return AnimatedBuilder( - animation: route.animation!, + animation: widget.route.animation!, builder: (BuildContext context, Widget? child) { return FadeTransition( - opacity: opacity.animate(route.animation!), + opacity: opacity.animate(widget.route.animation!), child: Material( - shape: route.shape ?? popupMenuTheme.shape ?? defaults.shape, - color: route.color ?? popupMenuTheme.color ?? defaults.color, - clipBehavior: clipBehavior, + shape: widget.route.shape ?? popupMenuTheme.shape ?? defaults.shape, + color: widget.route.color ?? popupMenuTheme.color ?? defaults.color, + clipBehavior: widget.clipBehavior, type: MaterialType.card, - elevation: route.elevation ?? + elevation: widget.route.elevation ?? popupMenuTheme.elevation ?? defaults.elevation!, - shadowColor: route.shadowColor ?? + shadowColor: widget.route.shadowColor ?? popupMenuTheme.shadowColor ?? defaults.shadowColor, - surfaceTintColor: route.surfaceTintColor ?? + surfaceTintColor: widget.route.surfaceTintColor ?? popupMenuTheme.surfaceTintColor ?? defaults.surfaceTintColor, child: Align( alignment: AlignmentDirectional.topEnd, - widthFactor: width.evaluate(route.animation!), - heightFactor: height.evaluate(route.animation!), + widthFactor: width.evaluate(widget.route.animation!), + heightFactor: height.evaluate(widget.route.animation!), child: child, ), ), @@ -686,6 +697,21 @@ class _PopupMenu extends StatelessWidget { child: child, ); } + + @override + void dispose() { + _kPopupMenuKey = null; + super.dispose(); + } + + void dismiss() { + if (_kPopupMenuKey == null) { + return; + } + + Navigator.of(context).pop(); + _kPopupMenuKey = null; + } } // Positioning of the menu on the screen. @@ -937,7 +963,9 @@ class _PopupMenuRoute extends PopupRoute { scrollTo(selectedItemIndex); } + _kPopupMenuKey ??= GlobalKey<_PopupMenuState>(); final Widget menu = _PopupMenu( + key: _kPopupMenuKey, route: this, itemKeys: itemKeys, semanticLabel: semanticLabel, @@ -1526,7 +1554,7 @@ class PopupMenuButtonState extends State> { if (widget.child != null) { return AnimatedGestureDetector( - scaleFactor: 0.99, + scaleFactor: 0.95, onTapUp: widget.enabled ? showButtonMenu : null, child: widget.child!, ); @@ -1607,3 +1635,12 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData { const EdgeInsets.symmetric(horizontal: 12.0); } // END GENERATED TOKEN PROPERTIES - PopupMenu + +extension PopupMenuColors on BuildContext { + Color get popupMenuBackgroundColor { + if (Theme.of(this).brightness == Brightness.light) { + return Theme.of(this).colorScheme.surface; + } + return const Color(0xFF23262B); + } +} diff --git a/frontend/resources/flowy_icons/40x/m_empty_trash.svg b/frontend/resources/flowy_icons/40x/m_empty_trash.svg new file mode 100644 index 0000000000000..1cb0d4ba15d7b --- /dev/null +++ b/frontend/resources/flowy_icons/40x/m_empty_trash.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 5129ba26c1b98..e24aaa09250d3 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -49,7 +49,7 @@ "syncPromptMessage": "Syncing the data might take a while. Please don't close this page", "or": "OR", "signInWithGoogle": "Continue with Google", - "signInWithGithub": "Continue with Github", + "signInWithGithub": "Continue with GitHub", "signInWithDiscord": "Continue with Discord", "signInWithApple": "Continue with Apple", "continueAnotherWay": "Continue another way", @@ -144,8 +144,8 @@ "rename": "Rename", "delete": "Delete", "duplicate": "Duplicate", - "unfavorite": "Remove from favorites", - "favorite": "Add to favorites", + "unfavorite": "Remove from Favorites", + "favorite": "Add to Favorites", "openNewTab": "Open in a new tab", "moveTo": "Move to", "addToFavorites": "Add to Favorites", @@ -204,8 +204,8 @@ }, "mobile": { "actions": "Trash Actions", - "empty": "Trash Bin is Empty", - "emptyDescription": "You don't have any deleted file", + "empty": "No pages or spaces in Trash", + "emptyDescription": "Move things you don't need to the Trash.", "isDeleted": "is deleted", "isRestored": "is restored" }, @@ -347,9 +347,9 @@ "putback": "Put Back", "update": "Update", "share": "Share", - "removeFromFavorites": "Remove from favorites", - "removeFromRecent": "Remove from recent", - "addToFavorites": "Add to favorites", + "removeFromFavorites": "Remove from Favorites", + "removeFromRecent": "Remove from Recent", + "addToFavorites": "Add to Favorites", "favoriteSuccessfully": "Favorited success", "unfavoriteSuccessfully": "Unfavorited success", "duplicateSuccessfully": "Duplicated successfully", @@ -368,7 +368,7 @@ "deleteAccount": "Delete account", "back": "Back", "signInGoogle": "Continue with Google", - "signInGithub": "Continue with Github", + "signInGithub": "Continue with GitHub", "signInDiscord": "Continue with Discord", "more": "More", "create": "Create", @@ -997,16 +997,16 @@ "archiveAll": "Archive all" }, "emptyInbox": { - "title": "No notifications yet", - "description": "You'll be notified here for @mentions" + "title": "Inbox Zero!", + "description": "Set reminders to receive notifications here." }, "emptyUnread": { "title": "No unread notifications", "description": "You're all caught up!" }, "emptyArchived": { - "title": "No archived notifications", - "description": "You haven't archived any notifications yet" + "title": "No archived", + "description": "Archived notifications will appear here." }, "tabs": { "inbox": "Inbox", @@ -2505,4 +2505,4 @@ "uploadFailedDescription": "The file upload failed", "uploadingDescription": "The file is being uploaded" } -} \ No newline at end of file +} From e85e092db8cc2ce358efd94219b5ea1b5837e853 Mon Sep 17 00:00:00 2001 From: KD-MM2 <57068549+KD-MM2@users.noreply.github.com> Date: Fri, 30 Aug 2024 20:24:43 +0900 Subject: [PATCH 08/60] chore: update translations for Vietnamese(vi-VN) (#6122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update translations with Fink 🐦 * chore: update translations with Fink 🐦 * chore: update translations with Fink 🐦 --- frontend/resources/translations/ar-SA.json | 10 +- frontend/resources/translations/ca-ES.json | 2 +- frontend/resources/translations/ckb-KU.json | 6 +- frontend/resources/translations/cs-CZ.json | 10 +- frontend/resources/translations/de-DE.json | 10 +- frontend/resources/translations/es-VE.json | 10 +- frontend/resources/translations/eu-ES.json | 2 +- frontend/resources/translations/fa.json | 2 +- frontend/resources/translations/fr-CA.json | 10 +- frontend/resources/translations/fr-FR.json | 14 +- frontend/resources/translations/he.json | 10 +- frontend/resources/translations/hu-HU.json | 2 +- frontend/resources/translations/id-ID.json | 6 +- frontend/resources/translations/it-IT.json | 10 +- frontend/resources/translations/ja-JP.json | 2 +- frontend/resources/translations/ko-KR.json | 2 +- frontend/resources/translations/pl-PL.json | 10 +- frontend/resources/translations/pt-BR.json | 10 +- frontend/resources/translations/pt-PT.json | 6 +- frontend/resources/translations/ru-RU.json | 10 +- frontend/resources/translations/sv-SE.json | 2 +- frontend/resources/translations/tr-TR.json | 10 +- frontend/resources/translations/uk-UA.json | 2 +- frontend/resources/translations/vi-VN.json | 1740 ++++++++++++++++++- frontend/resources/translations/vi.json | 2 +- frontend/resources/translations/zh-CN.json | 10 +- frontend/resources/translations/zh-TW.json | 10 +- 27 files changed, 1781 insertions(+), 139 deletions(-) diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index 5ab1c7107c6d3..997a4b543d765 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -407,8 +407,8 @@ "tooltipSelectIcon": "حدد أيقونة", "selectAnIcon": "حدد أيقونة", "pleaseInputYourOpenAIKey": "الرجاء إدخال مفتاح AI الخاص بك", - "pleaseInputYourStabilityAIKey": "يرجى إدخال رمز Stability AI الخاص بك", - "clickToLogout": "انقر لتسجيل خروج المستخدم الحالي" + "clickToLogout": "انقر لتسجيل خروج المستخدم الحالي", + "pleaseInputYourStabilityAIKey": "يرجى إدخال رمز Stability AI الخاص بك" }, "mobile": { "personalInfo": "معلومات شخصية", @@ -793,11 +793,11 @@ }, "searchForAnImage": "ابحث عن صورة", "pleaseInputYourOpenAIKey": "يرجى إدخال مفتاح AI الخاص بك في صفحة الإعدادات", - "pleaseInputYourStabilityAIKey": "يرجى إدخال مفتاح Stability AI الخاص بك في صفحة الإعدادات", "saveImageToGallery": "احفظ الصورة", "failedToAddImageToGallery": "فشلت إضافة الصورة إلى المعرض", "successToAddImageToGallery": "تمت إضافة الصورة إلى المعرض بنجاح", - "unableToLoadImage": "غير قادر على تحميل الصورة" + "unableToLoadImage": "غير قادر على تحميل الصورة", + "pleaseInputYourStabilityAIKey": "يرجى إدخال مفتاح Stability AI الخاص بك في صفحة الإعدادات" }, "codeBlock": { "language": { @@ -1169,4 +1169,4 @@ "addField": "إضافة حقل", "userIcon": "رمز المستخدم" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/ca-ES.json b/frontend/resources/translations/ca-ES.json index 3be6967df0816..a7a79854f704f 100644 --- a/frontend/resources/translations/ca-ES.json +++ b/frontend/resources/translations/ca-ES.json @@ -812,4 +812,4 @@ "deleteContentTitle": "Esteu segur que voleu suprimir {pageType}?", "deleteContentCaption": "si suprimiu aquest {pageType}, podeu restaurar-lo des de la paperera." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/ckb-KU.json b/frontend/resources/translations/ckb-KU.json index 264bf417c253b..e907dbabb0e59 100644 --- a/frontend/resources/translations/ckb-KU.json +++ b/frontend/resources/translations/ckb-KU.json @@ -481,8 +481,8 @@ "tooltipSelectIcon": "هەڵبژاەدنی وێنۆچكه‌", "selectAnIcon": "هەڵبژاردنی وێنۆچكه‌", "pleaseInputYourOpenAIKey": "تکایە کلیلی AI ـەکەت بنووسە", - "pleaseInputYourStabilityAIKey": "تکایە جێگیری کلیلی AI ـەکەت بنووسە", - "clickToLogout": "بۆ دەرچوون لە بەکارهێنەری ئێستا کلیک بکە" + "clickToLogout": "بۆ دەرچوون لە بەکارهێنەری ئێستا کلیک بکە", + "pleaseInputYourStabilityAIKey": "تکایە جێگیری کلیلی AI ـەکەت بنووسە" }, "mobile": { "personalInfo": "زانیاری کەسی", @@ -946,4 +946,4 @@ "frequentlyUsed": "زۆرجار بەکارت هێناوە" } } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/cs-CZ.json b/frontend/resources/translations/cs-CZ.json index 02f2a59d44c4f..07e5a01bead7f 100644 --- a/frontend/resources/translations/cs-CZ.json +++ b/frontend/resources/translations/cs-CZ.json @@ -379,8 +379,8 @@ "tooltipSelectIcon": "Vyberte ikonu", "selectAnIcon": "Vyberte ikonu", "pleaseInputYourOpenAIKey": "Prosím vložte svůj AI klíč", - "pleaseInputYourStabilityAIKey": "Prosím vložte svůj Stability AI klíč", - "clickToLogout": "Klin" + "clickToLogout": "Klin", + "pleaseInputYourStabilityAIKey": "Prosím vložte svůj Stability AI klíč" }, "mobile": { "personalInfo": "Osobní informace", @@ -736,11 +736,11 @@ }, "searchForAnImage": "Hledat obrázek", "pleaseInputYourOpenAIKey": "zadejte prosím svůj AI klíč v Nastavení", - "pleaseInputYourStabilityAIKey": "prosím vložte svůjStability AI klíč v Nastavení", "saveImageToGallery": "Uložit obrázek", "failedToAddImageToGallery": "Nepodařilo se přidat obrázek do galerie", "successToAddImageToGallery": "Obrázek byl úspěšně přidán do galerie", - "unableToLoadImage": "Nepodařilo se nahrát obrázek" + "unableToLoadImage": "Nepodařilo se nahrát obrázek", + "pleaseInputYourStabilityAIKey": "prosím vložte svůjStability AI klíč v Nastavení" }, "codeBlock": { "language": { @@ -1094,4 +1094,4 @@ "font": "Písmo", "actions": "Příkazy" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/de-DE.json b/frontend/resources/translations/de-DE.json index caedb89e14c9e..ce6efae28d02d 100644 --- a/frontend/resources/translations/de-DE.json +++ b/frontend/resources/translations/de-DE.json @@ -1197,8 +1197,8 @@ "tooltipSelectIcon": "Symbol auswählen", "selectAnIcon": "Ein Symbol auswählen", "pleaseInputYourOpenAIKey": "Bitte gebe den AI-Schlüssel ein", - "pleaseInputYourStabilityAIKey": "Bitte gebe den Stability AI Schlüssel ein", - "clickToLogout": "Klicken, um den aktuellen Nutzer auszulogen" + "clickToLogout": "Klicken, um den aktuellen Nutzer auszulogen", + "pleaseInputYourStabilityAIKey": "Bitte gebe den Stability AI Schlüssel ein" }, "mobile": { "personalInfo": "Persönliche Informationen", @@ -1742,7 +1742,6 @@ }, "searchForAnImage": "Nach einem Bild suchen", "pleaseInputYourOpenAIKey": "biitte den AI Schlüssel in der Einstellungsseite eingeben", - "pleaseInputYourStabilityAIKey": "biitte den Stability AI Schlüssel in der Einstellungsseite eingeben", "saveImageToGallery": "Bild speichern", "failedToAddImageToGallery": "Das Bild konnte nicht zur Galerie hinzugefügt werden", "successToAddImageToGallery": "Das Bild wurde zur Galerie hinzugefügt werden", @@ -1764,7 +1763,8 @@ "scalePercentage": "{}%", "deleteImageTooltip": "Bild löschen" } - } + }, + "pleaseInputYourStabilityAIKey": "biitte den Stability AI Schlüssel in der Einstellungsseite eingeben" }, "codeBlock": { "language": { @@ -2441,4 +2441,4 @@ "commentAddedSuccessfully": "Kommentar erfolgreich hinzugefügt.", "commentAddedSuccessTip": "Du hast gerade einen Kommentar hinzugefügt oder darauf geantwortet. Möchtest du nach oben springen, um die neuesten Kommentare zu sehen?" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/es-VE.json b/frontend/resources/translations/es-VE.json index 45b06906e75c5..e8df936ec8190 100644 --- a/frontend/resources/translations/es-VE.json +++ b/frontend/resources/translations/es-VE.json @@ -569,8 +569,8 @@ "tooltipSelectIcon": "Seleccionar icono", "selectAnIcon": "Seleccione un icono", "pleaseInputYourOpenAIKey": "por favor ingrese su clave AI", - "pleaseInputYourStabilityAIKey": "por favor ingrese su clave de estabilidad AI", - "clickToLogout": "Haga clic para cerrar la sesión del usuario actual." + "clickToLogout": "Haga clic para cerrar la sesión del usuario actual.", + "pleaseInputYourStabilityAIKey": "por favor ingrese su clave de estabilidad AI" }, "mobile": { "personalInfo": "Informacion personal", @@ -1049,14 +1049,14 @@ }, "searchForAnImage": "Buscar una imagen", "pleaseInputYourOpenAIKey": "ingresa tu clave AI en la página de Configuración", - "pleaseInputYourStabilityAIKey": "ingresa tu clave de Stability AI en la página de configuración", "saveImageToGallery": "Guardar imagen", "failedToAddImageToGallery": "No se pudo agregar la imagen a la galería", "successToAddImageToGallery": "Imagen agregada a la galería con éxito", "unableToLoadImage": "No se puede cargar la imagen", "maximumImageSize": "El tamaño máximo de imagen es de 10 MB", "uploadImageErrorImageSizeTooBig": "El tamaño de la imagen debe ser inferior a 10 MB.", - "imageIsUploading": "La imagen se está subiendo" + "imageIsUploading": "La imagen se está subiendo", + "pleaseInputYourStabilityAIKey": "ingresa tu clave de Stability AI en la página de configuración" }, "codeBlock": { "language": { @@ -1553,4 +1553,4 @@ "betaTooltip": "Actualmente solo admitimos la búsqueda de páginas.", "fromTrashHint": "De la papelera" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/eu-ES.json b/frontend/resources/translations/eu-ES.json index 031b9d93918ec..f5d7340102d05 100644 --- a/frontend/resources/translations/eu-ES.json +++ b/frontend/resources/translations/eu-ES.json @@ -601,4 +601,4 @@ "deleteContentTitle": "Ziur {pageType} ezabatu nahi duzula?", "deleteContentCaption": "{pageType} hau ezabatzen baduzu, zaborrontzitik leheneratu dezakezu." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/fa.json b/frontend/resources/translations/fa.json index 5baa50e8dde45..21c8505c88556 100644 --- a/frontend/resources/translations/fa.json +++ b/frontend/resources/translations/fa.json @@ -674,4 +674,4 @@ "frequentlyUsed": "استفاده‌شده" } } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/fr-CA.json b/frontend/resources/translations/fr-CA.json index 9a3f512de6a7a..06a9dc243219b 100644 --- a/frontend/resources/translations/fr-CA.json +++ b/frontend/resources/translations/fr-CA.json @@ -429,8 +429,8 @@ "tooltipSelectIcon": "Sélectionner l'icône", "selectAnIcon": "Sélectionnez une icône", "pleaseInputYourOpenAIKey": "Veuillez entrer votre clé AI", - "pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI", - "clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel" + "clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel", + "pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI" }, "mobile": { "personalInfo": "Informations personnelles", @@ -847,13 +847,13 @@ }, "searchForAnImage": "Rechercher une image", "pleaseInputYourOpenAIKey": "veuillez saisir votre clé AI dans la page Paramètres", - "pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres", "saveImageToGallery": "Enregistrer l'image", "failedToAddImageToGallery": "Échec de l'ajout d'une image à la galerie", "successToAddImageToGallery": "Image ajoutée à la galerie avec succès", "unableToLoadImage": "Impossible de charger l'image", "maximumImageSize": "La taille d'image maximale est 10Mo", - "uploadImageErrorImageSizeTooBig": "L'image doit faire moins de 10Mo" + "uploadImageErrorImageSizeTooBig": "L'image doit faire moins de 10Mo", + "pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres" }, "codeBlock": { "language": { @@ -1262,4 +1262,4 @@ "userIcon": "Icône utilisateur" }, "noLogFiles": "Il n'y a pas de log" -} \ No newline at end of file +} diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index e11bdebcedaea..8494f63b8f28f 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -1188,7 +1188,6 @@ "typeAValue": "Tapez une valeur...", "layout": "Mise en page", "databaseLayout": "Mise en page", - "viewList": "Vues de base de données", "editView": "Modifier vue", "boardSettings": "Paramètres du tableau", "calendarSettings": "Paramètres du calendrier", @@ -1196,7 +1195,8 @@ "duplicateView": "Dupliquer la vue", "deleteView": "Supprimer la vue", "numberOfVisibleFields": "{} affiché(s)", - "Properties": "Propriétés" + "Properties": "Propriétés", + "viewList": "Vues de base de données" }, "textFilter": { "contains": "Contient", @@ -1452,7 +1452,6 @@ "heading3": "Titre 3", "bulletedList": "Liste à puces", "numberedList": "Liste numérotée", - "checkbox": "Case à cocher", "linkedDoc": "Lien vers la page", "grid": "Grille", "linkedGrid": "Grille liée", @@ -1467,7 +1466,8 @@ "aiWriter": "Rédacteur IA", "dateOrReminder": "Date ou rappel", "photoGallery": "Galerie de photos", - "file": "Fichier" + "file": "Fichier", + "checkbox": "Case à cocher" } }, "selectionMenu": { @@ -1829,10 +1829,10 @@ "layoutDateField": "Calendrier de mise en page par", "changeLayoutDateField": "Modifier le champ de mise en page", "noDateTitle": "Pas de date", - "noDateHint": "Les événements non planifiés s'afficheront ici", "unscheduledEventsTitle": "Événements non planifiés", "clickToAdd": "Cliquez pour ajouter au calendrier", - "name": "Disposition du calendrier" + "name": "Disposition du calendrier", + "noDateHint": "Les événements non planifiés s'afficheront ici" }, "referencedCalendarPrefix": "Vue", "quickJumpYear": "Sauter à", @@ -2363,4 +2363,4 @@ "commentAddedSuccessfully": "Commentaire ajouté avec succès.", "commentAddedSuccessTip": "Vous venez d'ajouter ou de répondre à un commentaire. Souhaitez-vous passer en haut de la page pour voir les derniers commentaires ?" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/he.json b/frontend/resources/translations/he.json index b3819e41825e6..47f0bc0a0964f 100644 --- a/frontend/resources/translations/he.json +++ b/frontend/resources/translations/he.json @@ -996,8 +996,8 @@ "tooltipSelectIcon": "בחירת סמל", "selectAnIcon": "נא לבחור סמל", "pleaseInputYourOpenAIKey": "נא למלא את המפתח שלך ב־OpenAI", - "pleaseInputYourStabilityAIKey": "נא למלא את המפתח שלך ב־Stability AI", - "clickToLogout": "לחיצה תוציא את המשתמש הנוכחי" + "clickToLogout": "לחיצה תוציא את המשתמש הנוכחי", + "pleaseInputYourStabilityAIKey": "נא למלא את המפתח שלך ב־Stability AI" }, "mobile": { "personalInfo": "פרטים אישיים", @@ -1480,14 +1480,14 @@ }, "searchForAnImage": "חיפוש אחר תמונה", "pleaseInputYourOpenAIKey": "נא למלא את מפתח ה־OpenAI שלך בעמוד ההגדרות", - "pleaseInputYourStabilityAIKey": "נא למלא את המפתח ל־Stability AI בעמוד ההגדרות", "saveImageToGallery": "שמירת תמונה", "failedToAddImageToGallery": "הוספת התמונה לגלריה נכשלה", "successToAddImageToGallery": "התמונה נוספה לגלריה בהצלחה", "unableToLoadImage": "לא ניתן לטעון את התמונה", "maximumImageSize": "גודל ההעלאה המרבי הנתמך הוא 10 מ״ב", "uploadImageErrorImageSizeTooBig": "גודל התמונה חייב להיות גדול מ־10 מ״ב", - "imageIsUploading": "התמונה נשלחת" + "imageIsUploading": "התמונה נשלחת", + "pleaseInputYourStabilityAIKey": "נא למלא את המפתח ל־Stability AI בעמוד ההגדרות" }, "codeBlock": { "language": { @@ -2086,4 +2086,4 @@ "signInError": "שגיאת כניסה", "login": "הרשמה או כניסה" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/hu-HU.json b/frontend/resources/translations/hu-HU.json index 78134fd8abf9f..3fcae71dfc15f 100644 --- a/frontend/resources/translations/hu-HU.json +++ b/frontend/resources/translations/hu-HU.json @@ -599,4 +599,4 @@ "deleteContentTitle": "Biztosan törli a következőt: {pageType}?", "deleteContentCaption": "ha törli ezt a {pageType} oldalt, visszaállíthatja a kukából." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/id-ID.json b/frontend/resources/translations/id-ID.json index 011da148146b7..2506695bb8525 100644 --- a/frontend/resources/translations/id-ID.json +++ b/frontend/resources/translations/id-ID.json @@ -377,8 +377,8 @@ "tooltipSelectIcon": "Pilih ikon", "selectAnIcon": "Pilih ikon", "pleaseInputYourOpenAIKey": "silakan masukkan kunci AI Anda", - "pleaseInputYourStabilityAIKey": "Masukkan kunci Stability AI anda", - "clickToLogout": "Klik untuk keluar dari pengguna saat ini" + "clickToLogout": "Klik untuk keluar dari pengguna saat ini", + "pleaseInputYourStabilityAIKey": "Masukkan kunci Stability AI anda" }, "mobile": { "personalInfo": "Informasi pribadi", @@ -1022,4 +1022,4 @@ "noFavorite": "Tidak ada halaman favorit", "noFavoriteHintText": "Geser halaman ke kiri untuk menambahkannya ke favorit Anda" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/it-IT.json b/frontend/resources/translations/it-IT.json index 3d56f7196a1c7..3233c67fd617b 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -428,8 +428,8 @@ "tooltipSelectIcon": "Seleziona l'icona", "selectAnIcon": "Seleziona un'icona", "pleaseInputYourOpenAIKey": "inserisci la tua chiave AI", - "pleaseInputYourStabilityAIKey": "per favore inserisci la tua chiave Stability AI", - "clickToLogout": "Fare clic per disconnettere l'utente corrente" + "clickToLogout": "Fare clic per disconnettere l'utente corrente", + "pleaseInputYourStabilityAIKey": "per favore inserisci la tua chiave Stability AI" }, "mobile": { "personalInfo": "Informazione personale", @@ -854,14 +854,14 @@ }, "searchForAnImage": "Cerca un'immagine", "pleaseInputYourOpenAIKey": "inserisci la tua chiave AI nella pagina Impostazioni", - "pleaseInputYourStabilityAIKey": "inserisci la chiave Stability AI nella pagina Impostazioni", "saveImageToGallery": "Salva immagine", "failedToAddImageToGallery": "Impossibile aggiungere l'immagine alla galleria", "successToAddImageToGallery": "Immagine aggiunta alla galleria con successo", "unableToLoadImage": "Impossibile caricare l'immagine", "maximumImageSize": "La dimensione massima supportata per il caricamento delle immagini è di 10 MB", "uploadImageErrorImageSizeTooBig": "Le dimensioni dell'immagine devono essere inferiori a 10 MB", - "imageIsUploading": "L'immagine si sta caricando" + "imageIsUploading": "L'immagine si sta caricando", + "pleaseInputYourStabilityAIKey": "inserisci la chiave Stability AI nella pagina Impostazioni" }, "codeBlock": { "language": { @@ -1262,4 +1262,4 @@ "userIcon": "Icona utente" }, "noLogFiles": "Non ci sono file di log" -} \ No newline at end of file +} diff --git a/frontend/resources/translations/ja-JP.json b/frontend/resources/translations/ja-JP.json index 3de71dd7d771f..0b00dfa9c8321 100644 --- a/frontend/resources/translations/ja-JP.json +++ b/frontend/resources/translations/ja-JP.json @@ -690,4 +690,4 @@ "deleteContentTitle": "{pageType} を削除してもよろしいですか?", "deleteContentCaption": "この {pageType} を削除しても、ゴミ箱から復元できます。" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/ko-KR.json b/frontend/resources/translations/ko-KR.json index a9d97d944d488..823792b2a4be0 100644 --- a/frontend/resources/translations/ko-KR.json +++ b/frontend/resources/translations/ko-KR.json @@ -598,4 +598,4 @@ "deleteContentTitle": "{pageType}을(를) 삭제하시겠습니까?", "deleteContentCaption": "이 {pageType}을(를) 삭제하면 휴지통에서 복원할 수 있습니다." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/pl-PL.json b/frontend/resources/translations/pl-PL.json index 1347de88e34e2..15d28b5bd09e3 100644 --- a/frontend/resources/translations/pl-PL.json +++ b/frontend/resources/translations/pl-PL.json @@ -447,8 +447,8 @@ "tooltipSelectIcon": "Wybierz ikonę", "selectAnIcon": "Wybierz ikonę", "pleaseInputYourOpenAIKey": "wprowadź swój klucz AI", - "pleaseInputYourStabilityAIKey": "wprowadź swój klucz Stability AI", - "clickToLogout": "Kliknij, aby wylogować bieżącego użytkownika" + "clickToLogout": "Kliknij, aby wylogować bieżącego użytkownika", + "pleaseInputYourStabilityAIKey": "wprowadź swój klucz Stability AI" }, "mobile": { "personalInfo": "Informacje Osobiste", @@ -796,11 +796,11 @@ }, "searchForAnImage": "Szukaj obrazu", "pleaseInputYourOpenAIKey": "wpisz swój klucz AI w ustawieniach", - "pleaseInputYourStabilityAIKey": "wpisz swój klucz Stability AI w ustawieniach", "saveImageToGallery": "Zapisz obraz", "failedToAddImageToGallery": "Nie udało się dodać obrazu do galerii", "successToAddImageToGallery": "Pomyślnie dodano obraz do galerii", - "unableToLoadImage": "Nie udało się wczytać obrazu" + "unableToLoadImage": "Nie udało się wczytać obrazu", + "pleaseInputYourStabilityAIKey": "wpisz swój klucz Stability AI w ustawieniach" }, "codeBlock": { "language": { @@ -1146,4 +1146,4 @@ "language": "Język", "font": "Czcionka" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/pt-BR.json b/frontend/resources/translations/pt-BR.json index fc7caabfc0892..abbd35605eab4 100644 --- a/frontend/resources/translations/pt-BR.json +++ b/frontend/resources/translations/pt-BR.json @@ -422,8 +422,8 @@ "tooltipSelectIcon": "Selecionar ícone", "selectAnIcon": "Escolha um ícone", "pleaseInputYourOpenAIKey": "por favor insira sua chave AI", - "pleaseInputYourStabilityAIKey": "insira sua chave Stability AI", - "clickToLogout": "Clique para sair do usuário atual" + "clickToLogout": "Clique para sair do usuário atual", + "pleaseInputYourStabilityAIKey": "insira sua chave Stability AI" }, "mobile": { "personalInfo": "Informações pessoais", @@ -838,11 +838,11 @@ }, "searchForAnImage": "Procurar uma imagem", "pleaseInputYourOpenAIKey": "insira sua chave AI na página configurações", - "pleaseInputYourStabilityAIKey": "insira sua chave Stability AI na página Configurações", "saveImageToGallery": "Salvar imagem", "failedToAddImageToGallery": "Falha ao adicionar imagem à galeria", "successToAddImageToGallery": "Imagem adicionada à galeria com sucesso", - "unableToLoadImage": "Não foi possível carregar a imagem" + "unableToLoadImage": "Não foi possível carregar a imagem", + "pleaseInputYourStabilityAIKey": "insira sua chave Stability AI na página Configurações" }, "codeBlock": { "language": { @@ -1219,4 +1219,4 @@ "addField": "Adicionar campo", "userIcon": "Ícone do usuário" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/pt-PT.json b/frontend/resources/translations/pt-PT.json index 62f60ef702d77..d1b43730ee2ca 100644 --- a/frontend/resources/translations/pt-PT.json +++ b/frontend/resources/translations/pt-PT.json @@ -363,8 +363,8 @@ "tooltipSelectIcon": "Selecione o ícone", "selectAnIcon": "Selecione um ícone", "pleaseInputYourOpenAIKey": "por favor insira sua chave AI", - "pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI", - "clickToLogout": "Clique para fazer logout" + "clickToLogout": "Clique para fazer logout", + "pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI" }, "shortcuts": { "shortcutsLabel": "Atalhos", @@ -857,4 +857,4 @@ "noResult": "Nenhum resultado", "caseSensitive": "Maiúsculas e minúsculas" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/ru-RU.json b/frontend/resources/translations/ru-RU.json index 45861fcd69b2b..5e9b8e57f5e53 100644 --- a/frontend/resources/translations/ru-RU.json +++ b/frontend/resources/translations/ru-RU.json @@ -1000,8 +1000,8 @@ "tooltipSelectIcon": "Выберите иконку", "selectAnIcon": "Выбрать иконку", "pleaseInputYourOpenAIKey": "Пожалуйста, введите токен AI", - "pleaseInputYourStabilityAIKey": "Пожалуйста, введите свой токен Stability AI", - "clickToLogout": "Нажмите, чтобы выйти из текущего аккаунта" + "clickToLogout": "Нажмите, чтобы выйти из текущего аккаунта", + "pleaseInputYourStabilityAIKey": "Пожалуйста, введите свой токен Stability AI" }, "mobile": { "personalInfo": "Личная информация", @@ -1496,14 +1496,14 @@ }, "searchForAnImage": "Поиск изображения", "pleaseInputYourOpenAIKey": "пожалуйста, введите свой токен AI на странице настроек", - "pleaseInputYourStabilityAIKey": "пожалуйста, введите свой токен Stability AI на странице настроек", "saveImageToGallery": "Сохранить изображение", "failedToAddImageToGallery": "Ошибка добавления изображения в галерею", "successToAddImageToGallery": "Изображение успешно добавлено", "unableToLoadImage": "Ошибка загрузки изображения", "maximumImageSize": "Максимальный поддерживаемый размер загружаемого изображения — 10 МБ.", "uploadImageErrorImageSizeTooBig": "Размер изображения должен быть меньше 10 МБ.", - "imageIsUploading": "Изображение загружается" + "imageIsUploading": "Изображение загружается", + "pleaseInputYourStabilityAIKey": "пожалуйста, введите свой токен Stability AI на странице настроек" }, "codeBlock": { "language": { @@ -2103,4 +2103,4 @@ "signInError": "Ошибка входа", "login": "Зарегистрироваться или войти" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/sv-SE.json b/frontend/resources/translations/sv-SE.json index 6284a61d9a7a2..dc48bf92c8af4 100644 --- a/frontend/resources/translations/sv-SE.json +++ b/frontend/resources/translations/sv-SE.json @@ -668,4 +668,4 @@ "deleteContentTitle": "Är du säker på att du vill ta bort {pageType}?", "deleteContentCaption": "om du tar bort denna {pageType} kan du återställa den från papperskorgen." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/tr-TR.json b/frontend/resources/translations/tr-TR.json index 20975c33e8764..e76aaf2c49657 100644 --- a/frontend/resources/translations/tr-TR.json +++ b/frontend/resources/translations/tr-TR.json @@ -1095,8 +1095,8 @@ "tooltipSelectIcon": "Simge seç", "selectAnIcon": "Bir simge seçin", "pleaseInputYourOpenAIKey": "Lütfen Yapay Zeka anahtarınızı girin", - "pleaseInputYourStabilityAIKey": "Lütfen Stability Yapay Zeka anahtarınızı girin", - "clickToLogout": "Geçerli kullanıcıdan çıkış yapmak için tıklayın" + "clickToLogout": "Geçerli kullanıcıdan çıkış yapmak için tıklayın", + "pleaseInputYourStabilityAIKey": "Lütfen Stability Yapay Zeka anahtarınızı girin" }, "mobile": { "personalInfo": "Kişisel Bilgiler", @@ -1594,7 +1594,6 @@ }, "searchForAnImage": "Bir resim arayın", "pleaseInputYourOpenAIKey": "Lütfen Ayarlar sayfasında Yapay Zeka anahtarınızı girin", - "pleaseInputYourStabilityAIKey": "Lütfen Ayarlar sayfasında Stability Yapay Zeka anahtarınızı girin", "saveImageToGallery": "Resmi kaydet", "failedToAddImageToGallery": "Resim galeriye eklenemedi", "successToAddImageToGallery": "Resim başarıyla galeriye eklendi", @@ -1616,7 +1615,8 @@ "scalePercentage": "%{}", "deleteImageTooltip": "Resmi sil" } - } + }, + "pleaseInputYourStabilityAIKey": "Lütfen Ayarlar sayfasında Stability Yapay Zeka anahtarınızı girin" }, "codeBlock": { "language": { @@ -2226,4 +2226,4 @@ "signInError": "Oturum açma hatası", "login": "Kaydolun veya giriş yapın" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/uk-UA.json b/frontend/resources/translations/uk-UA.json index 7aed531f3bf34..60b929e864c34 100644 --- a/frontend/resources/translations/uk-UA.json +++ b/frontend/resources/translations/uk-UA.json @@ -813,4 +813,4 @@ "noResult": "Немає результатів", "caseSensitive": "З урахуванням регістру" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/vi-VN.json b/frontend/resources/translations/vi-VN.json index 0f390ab2ff42f..5c987c14888e7 100644 --- a/frontend/resources/translations/vi-VN.json +++ b/frontend/resources/translations/vi-VN.json @@ -26,7 +26,7 @@ "repeatPasswordEmptyError": "Mật khẩu nhập lại không được để trống", "unmatchedPasswordError": "Mật khẩu nhập lại không giống mật khẩu", "alreadyHaveAnAccount": "Đã có tài khoản?", - "emailHint": "Email", + "emailHint": "E-mail", "passwordHint": "Mật khẩu", "repeatPasswordHint": "Nhập lại mật khẩu", "signUpWith": "Đăng ký với:" @@ -36,18 +36,39 @@ "loginButtonText": "Đăng nhập", "loginStartWithAnonymous": "Bắt đầu với một phiên ẩn danh", "continueAnonymousUser": "Tiếp tục với một phiên ẩn danh", + "anonymous": "Ẩn danh", "buttonText": "Đăng nhập", "signingInText": "Đang đăng nhập...", "forgotPassword": "Quên mật khẩu?", - "emailHint": "Email", + "emailHint": "E-mail", "passwordHint": "Mật khẩu", "dontHaveAnAccount": "Bạn chưa có tài khoản?", + "createAccount": "Tạo tài khoản", "repeatPasswordEmptyError": "Mật khẩu nhập lại không được để trống", "unmatchedPasswordError": "Mật khẩu nhập lại không giống mật khẩu", "syncPromptMessage": "Việc đồng bộ hóa dữ liệu có thể mất một lúc. Xin đừng đóng trang này", "or": "HOẶC", + "signInWithGoogle": "Tiếp tục với Google", + "signInWithGithub": "Tiếp tục với Github", + "signInWithDiscord": "Tiếp tục với Discord", + "signInWithApple": "Tiếp tục với Apple", + "continueAnotherWay": "Tiếp tục theo cách khác", + "signUpWithGoogle": "Đăng ký với Google", + "signUpWithGithub": "Đăng ký với Github", + "signUpWithDiscord": "Đăng ký với Discord", "signInWith": "Đăng nhập bằng:", "signInWithEmail": "Đăng nhập bằng Email", + "signInWithMagicLink": "Tiếp tục", + "signUpWithMagicLink": "Đăng ký với Magic Link", + "pleaseInputYourEmail": "Vui lòng nhập địa chỉ email của bạn", + "settings": "Cài đặt", + "magicLinkSent": "Đã gửi Magic Link!", + "invalidEmail": "Vui lòng nhập địa chỉ email hợp lệ", + "alreadyHaveAnAccount": "Bạn đã có tài khoản?", + "logIn": "Đăng nhập", + "generalError": "Có gì đó không ổn. Vui lòng thử lại sau", + "limitRateError": "Vì lý do bảo mật, bạn chỉ có thể yêu cầu Magic Link sau mỗi 60 giây", + "magicLinkSentDescription": "Một Magic Link đã được gửi đến email của bạn. Nhấp vào liên kết để hoàn tất đăng nhập. Liên kết sẽ hết hạn sau 5 phút.", "LogInWithGoogle": "Đăng nhập bằng Google", "LogInWithGithub": "Đăng nhập bằng Github", "LogInWithDiscord": "Đăng nhập bằng Discord", @@ -57,6 +78,7 @@ "chooseWorkspace": "Chọn không gian làm việc của bạn", "create": "Tạo không gian làm việc", "reset": "Đặt lại không gian làm việc", + "renameWorkspace": "Đổi tên không gian làm việc", "resetWorkspacePrompt": "Đặt lại không gian làm việc sẽ xóa tất cả các trang và dữ liệu trong đó. Bạn có chắc chắn muốn đặt lại không gian làm việc? Ngoài ra, bạn có thể liên hệ với nhóm hỗ trợ để khôi phục không gian làm việc", "hint": "không gian làm việc", "notFoundError": "Không tìm thấy không gian làm việc", @@ -89,8 +111,18 @@ "buttonText": "Chia sẻ", "workInProgress": "Sắp ra mắt", "markdown": "Markdown", + "html": "HTML", + "clipboard": "Sao chép vào clipboard", "csv": "CSV", - "copyLink": "Sao chép đường dẫn" + "copyLink": "Sao chép đường dẫn", + "publishToTheWeb": "Xuất bản lên Web", + "publishToTheWebHint": "Tạo trang web với AppFlowy", + "publish": "Xuất bản", + "unPublish": "Hủy xuất bản", + "visitSite": "Truy cập trang web", + "exportAsTab": "Xuất khẩu dưới dạng", + "publishTab": "Xuất bản", + "shareTab": "Chia sẻ" }, "moreAction": { "small": "nhỏ", @@ -121,7 +153,9 @@ "openNewTab": "Mở trong tab mới", "moveTo": "Chuyển tới", "addToFavorites": "Thêm vào mục yêu thích", - "copyLink": "Sao chép đường dẫn" + "copyLink": "Sao chép đường dẫn", + "changeIcon": "Thay đổi biểu tượng", + "collapseAllPages": "Thu gọn tất cả các trang con" }, "blankPageTitle": "Trang trống", "newPageText": "Trang mới", @@ -129,6 +163,32 @@ "newGridText": "Lưới mới", "newCalendarText": "Lịch mới", "newBoardText": "Bảng mới", + "chat": { + "newChat": "AI Chat", + "inputMessageHint": "Hỏi @:appName AI", + "inputLocalAIMessageHint": "Hỏi @:appName AI cục bộ", + "unsupportedCloudPrompt": "Tính năng này chỉ khả dụng khi sử dụng @:appName Cloud", + "relatedQuestion": "Có liên quan", + "serverUnavailable": "Dịch vụ tạm thời không khả dụng. Vui lòng thử lại sau.", + "aiServerUnavailable": "🌈 Ồ không! 🌈. Một con kỳ lân đã ăn mất câu trả lời của chúng tôi. Vui lòng thử lại!", + "clickToRetry": "Nhấp để thử lại", + "regenerateAnswer": "Tạo lại", + "question1": "Cách sử dụng Kanban để quản lý nhiệm vụ", + "question2": "Giải thích phương pháp GTD", + "question3": "Tại sao sử dụng Rust", + "question4": "Công thức với những gì đang có", + "aiMistakePrompt": "AI có thể mắc lỗi. Hãy kiểm tra thông tin quan trọng.", + "chatWithFilePrompt": "Bạn có muốn trò chuyện với tập tin không?", + "indexFileSuccess": "Đang lập chỉ mục tệp thành công", + "inputActionNoPages": "Không có kết quả trang", + "referenceSource": "{} nguồn đã tìm thấy", + "referenceSources": "{} nguồn được tìm thấy", + "clickToMention": "Nhấp để đề cập đến một trang", + "uploadFile": "Tải lên các tệp PDF, md hoặc txt để trò chuyện", + "questionTitle": "Ý tưởng", + "questionDetail": "Xin chào {}! Tôi có thể giúp gì cho bạn hôm nay?", + "indexingFile": "Đang lập chỉ mục {}" + }, "trash": { "text": "Thùng rác", "restoreAll": "Khôi phục lại tất cả", @@ -190,6 +250,8 @@ "numList": "Danh sách đánh số", "bulletList": "Danh sách có dấu đầu dòng", "checkList": "Danh mục", + "inlineCode": "Mã nội tuyến", + "quote": "Khối trích dẫn", "header": "Tiêu đề", "highlight": "Điểm nổi bật", "color": "Màu", @@ -205,7 +267,8 @@ "dragRow": "Nhấn và giữ để sắp xếp lại hàng", "viewDataBase": "Xem cơ sở dữ liệu", "referencePage": "{name} này được tham chiếu", - "addBlockBelow": "Thêm một khối bên dưới" + "addBlockBelow": "Thêm một khối bên dưới", + "aiGenerate": "Tạo" }, "sideBar": { "closeSidebar": "Đóng thanh bên", @@ -214,10 +277,43 @@ "private": "Riêng tư", "workspace": "Không gian làm việc", "favorites": "Yêu thích", + "clickToHidePrivate": "Nhấp để ẩn không gian riêng tư\nCác trang bạn tạo ở đây chỉ hiển thị với bạn", + "clickToHideWorkspace": "Nhấp để ẩn không gian làm việc\nCác trang bạn tạo ở đây có thể được mọi thành viên nhìn thấy", "clickToHidePersonal": "Bấm để ẩn mục riêng tư", "clickToHideFavorites": "Bấm để ẩn mục yêu thích", "addAPage": "Thêm một trang", - "recent": "Gần đây" + "addAPageToPrivate": "Thêm một trang vào không gian riêng tư", + "addAPageToWorkspace": "Thêm một trang vào không gian làm việc", + "recent": "Gần đây", + "today": "Hôm nay", + "thisWeek": "Tuần này", + "others": "Yêu thích trước đó", + "justNow": "ngay bây giờ", + "minutesAgo": "{count} phút trước", + "lastViewed": "Đã xem lần cuối", + "favoriteAt": "Đã yêu thích", + "emptyRecent": "Không có tài liệu gần đây", + "emptyRecentDescription": "Khi bạn xem tài liệu, chúng sẽ xuất hiện ở đây để dễ dàng truy xuất", + "emptyFavorite": "Không có tài liệu yêu thích", + "emptyFavoriteDescription": "Bắt đầu khám phá và đánh dấu tài liệu là mục yêu thích. Chúng sẽ được liệt kê ở đây để truy cập nhanh!", + "removePageFromRecent": "Xóa trang này khỏi mục Gần đây?", + "removeSuccess": "Đã xóa thành công", + "favoriteSpace": "Yêu thích", + "RecentSpace": "Gần đây", + "Spaces": "Khoảng cách", + "upgradeToPro": "Nâng cấp lên Pro", + "upgradeToAIMax": "Mở khóa AI không giới hạn", + "storageLimitDialogTitle": "Bạn đã hết dung lượng lưu trữ miễn phí. Nâng cấp để mở khóa dung lượng lưu trữ không giới hạn", + "aiResponseLimitTitle": "Bạn đã hết phản hồi AI miễn phí. Nâng cấp lên Gói Pro hoặc mua tiện ích bổ sung AI để mở khóa phản hồi không giới hạn", + "aiResponseLimitDialogTitle": "Đã đạt đến giới hạn sử dụng AI", + "aiResponseLimit": "Bạn đã hết lượt dùng AI miễn phí.\nVào Cài đặt -> Gói đăng ký -> Nhấp vào AI Max hoặc Gói Pro để có thêm lượt dùng AI", + "askOwnerToUpgradeToPro": "Không gian làm việc của bạn sắp hết dung lượng lưu trữ miễn phí. Vui lòng yêu cầu chủ sở hữu không gian làm việc của bạn nâng cấp lên Gói Pro", + "askOwnerToUpgradeToAIMax": "Không gian làm việc của bạn sắp hết phản hồi AI miễn phí. Vui lòng yêu cầu chủ sở hữu không gian làm việc của bạn nâng cấp gói hoặc mua tiện ích bổ sung AI", + "purchaseStorageSpace": "Mua không gian lưu trữ", + "purchaseAIResponse": "Mua ", + "askOwnerToUpgradeToLocalAI": "Yêu cầu chủ sở hữu không gian làm việc bật AI trên thiết bị", + "upgradeToAILocal": "Chạy các mô hình cục bộ trên thiết bị của bạn để có quyền riêng tư tối đa", + "upgradeToAILocalDesc": "Trò chuyện với PDF, cải thiện khả năng viết và tự động điền bảng bằng AI cục bộ" }, "notifications": { "export": { @@ -233,6 +329,7 @@ }, "button": { "ok": "OK", + "confirm": "Xác nhận", "done": "Xong", "cancel": "Hủy", "signIn": "Đăng nhập", @@ -255,19 +352,35 @@ "update": "Cập nhật", "share": "Chia sẻ", "removeFromFavorites": "Loại bỏ khỏi mục yêu thích", + "removeFromRecent": "Xóa khỏi gần đây", "addToFavorites": "Thêm vào mục yêu thích", + "favoriteSuccessfully": "Đã thêm vào yêu thích", + "unfavoriteSuccessfully": "Đã xoá khỏi yêu thích", + "duplicateSuccessfully": "Đã sao chép thành công", "rename": "Đổi tên", "helpCenter": "Trung tâm trợ giúp", "add": "Thêm", "yes": "Đúng", + "no": "Không", + "clear": "Xoá", + "remove": "Di chuyển", + "dontRemove": "Không xóa", "copyLink": "Sao chép đường dẫn", "align": "Căn chỉnh", "login": "Đăng nhập", "logout": "Đăng xuất", "deleteAccount": "Xóa tài khoản", + "back": "Quay lại", "signInGoogle": "Đăng nhập bằng Google", "signInGithub": "Đăng nhập bằng Github", "signInDiscord": "Đăng nhập bằng Discord", + "more": "Hơn nữa", + "create": "Tạo mới", + "close": "Đóng", + "next": "Kế tiếp", + "previous": "Trước đó", + "submit": "Gửi", + "download": "Tải về", "tryAGain": "Thử lại" }, "label": { @@ -292,6 +405,517 @@ }, "settings": { "title": "Cài đặt", + "popupMenuItem": { + "settings": "Cài đặt", + "members": "Thành viên", + "trash": "Thùng Rác", + "helpAndSupport": "Trợ giúp & Hỗ trợ" + }, + "accountPage": { + "menuLabel": "Tài khoản của tôi", + "title": "Tài khoản của tôi", + "general": { + "title": "Tên tài khoản & ảnh đại diện", + "changeProfilePicture": "Thay đổi ảnh đại diện" + }, + "email": { + "title": "E-mail", + "actions": { + "change": "Thay đổi email" + } + }, + "login": { + "title": "Đăng nhập tài khoản", + "loginLabel": "Đăng nhập", + "logoutLabel": "Đăng xuất" + } + }, + "workspacePage": { + "menuLabel": "Không gian làm việc", + "title": "Không gian làm việc", + "description": "Tùy chỉnh giao diện không gian làm việc, chủ đề, phông chữ, bố cục văn bản, định dạng ngày/giờ và ngôn ngữ.", + "workspaceName": { + "title": "Tên không gian làm việc" + }, + "workspaceIcon": { + "title": "Biểu tượng không gian làm việc", + "description": "Tải lên hình ảnh hoặc sử dụng biểu tượng cảm xúc cho không gian làm việc của bạn. Biểu tượng sẽ hiển thị trên thanh bên và thông báo của bạn." + }, + "appearance": { + "title": "Vẻ bề ngoài", + "description": "Tùy chỉnh giao diện không gian làm việc, chủ đề, phông chữ, bố cục văn bản, ngày, giờ và ngôn ngữ.", + "options": { + "system": "Tự động", + "light": "Sáng", + "dark": "Tối" + } + }, + "resetCursorColor": { + "title": "Đặt lại màu con trỏ tài liệu", + "description": "Bạn có chắc chắn muốn thiết lập lại màu con trỏ không?" + }, + "resetSelectionColor": { + "title": "Đặt lại màu lựa chọn tài liệu", + "description": "Bạn có chắc chắn muốn thiết lập lại màu đã chọn không?" + }, + "theme": { + "title": "Chủ đề", + "description": "Chọn chủ đề có sẵn hoặc tải lên chủ đề tùy chỉnh của riêng bạn.", + "uploadCustomThemeTooltip": "Tải lên một chủ đề tùy chỉnh" + }, + "workspaceFont": { + "title": "Phông chữ không gian làm việc", + "noFontHint": "Không tìm thấy phông chữ, hãy thử thuật ngữ khác." + }, + "textDirection": { + "title": "Hướng văn bản", + "leftToRight": "Từ trái sang phải", + "rightToLeft": "Từ phải sang trái", + "auto": "Tự động", + "enableRTLItems": "Bật các mục thanh công cụ RTL" + }, + "layoutDirection": { + "title": "Hướng bố trí", + "leftToRight": "Từ trái sang phải", + "rightToLeft": "Từ phải sang trái" + }, + "dateTime": { + "title": "Ngày & giờ", + "example": "{} tại {} ({})", + "24HourTime": "thời gian 24 giờ", + "dateFormat": { + "label": "Định dạng ngày tháng", + "local": "Địa phương", + "us": "US", + "iso": "ISO", + "friendly": "Thân thiện", + "dmy": "D/M/Y" + } + }, + "language": { + "title": "Ngôn ngữ" + }, + "deleteWorkspacePrompt": { + "title": "Xóa không gian làm việc", + "content": "Bạn có chắc chắn muốn xóa không gian làm việc này không? Hành động này không thể hoàn tác và bất kỳ trang nào bạn đã xuất bản sẽ bị hủy xuất bản." + }, + "leaveWorkspacePrompt": { + "title": "Rời khỏi không gian làm việc", + "content": "Bạn có chắc chắn muốn rời khỏi không gian làm việc này không? Bạn sẽ mất quyền truy cập vào tất cả các trang và dữ liệu trong đó." + }, + "manageWorkspace": { + "title": "Quản lý không gian làm việc", + "leaveWorkspace": "Rời khỏi không gian làm việc", + "deleteWorkspace": "Xóa không gian làm việc" + } + }, + "manageDataPage": { + "menuLabel": "Quản lý dữ liệu", + "title": "Quản lý dữ liệu", + "description": "Quản lý dữ liệu lưu trữ cục bộ hoặc Nhập dữ liệu hiện có của bạn vào @:appName .", + "dataStorage": { + "title": "Vị trí lưu trữ tập tin", + "tooltip": "Vị trí lưu trữ các tập tin của bạn", + "actions": { + "change": "Thay đổi đường dẫn", + "open": "Mở thư mục", + "openTooltip": "Mở vị trí thư mục dữ liệu hiện tại", + "copy": "Sao chép đường dẫn", + "copiedHint": "Đã sao chép đường dẫn!", + "resetTooltip": "Đặt lại về vị trí mặc định" + }, + "resetDialog": { + "title": "Bạn có chắc không?", + "description": "Đặt lại đường dẫn đến vị trí dữ liệu mặc định sẽ không xóa dữ liệu của bạn. Nếu bạn muốn nhập lại dữ liệu hiện tại, trước tiên bạn nên sao chép đường dẫn đến vị trí hiện tại của mình." + } + }, + "importData": { + "title": "Nhập dữ liệu", + "tooltip": "Nhập dữ liệu từ các thư mục sao lưu/dữ liệu @:appName", + "description": "Sao chép dữ liệu từ thư mục dữ liệu @:appName bên ngoài", + "action": "Duyệt tập tin" + }, + "encryption": { + "title": "Mã hóa", + "tooltip": "Quản lý cách dữ liệu của bạn được lưu trữ và mã hóa", + "descriptionNoEncryption": "Bật mã hóa sẽ mã hóa toàn bộ dữ liệu. Không thể hoàn tác thao tác này.", + "descriptionEncrypted": "Dữ liệu của bạn được mã hóa.", + "action": "Mã hóa dữ liệu", + "dialog": { + "title": "Mã hóa toàn bộ dữ liệu của bạn?", + "description": "Mã hóa tất cả dữ liệu của bạn sẽ giữ cho dữ liệu của bạn an toàn và bảo mật. Hành động này KHÔNG THỂ hoàn tác. Bạn có chắc chắn muốn tiếp tục không?" + } + }, + "cache": { + "title": "Xóa bộ nhớ đệm", + "description": "Giúp giải quyết các vấn đề như hình ảnh không tải được, thiếu trang trong một khoảng trắng và phông chữ không tải được. Điều này sẽ không ảnh hưởng đến dữ liệu của bạn.", + "dialog": { + "title": "Xóa bộ nhớ đệm", + "description": "Giúp giải quyết các vấn đề như hình ảnh không tải được, thiếu trang trong một khoảng trắng và phông chữ không tải được. Điều này sẽ không ảnh hưởng đến dữ liệu của bạn.", + "successHint": "Đã xóa bộ nhớ đệm!" + } + }, + "data": { + "fixYourData": "Sửa dữ liệu của bạn", + "fixButton": "Sửa", + "fixYourDataDescription": "Nếu bạn gặp sự cố với dữ liệu, bạn có thể thử khắc phục tại đây." + } + }, + "shortcutsPage": { + "menuLabel": "Phím tắt", + "title": "Phím tắt", + "editBindingHint": "Nhập phím tắt mới", + "searchHint": "Tìm kiếm", + "actions": { + "resetDefault": "Đặt lại mặc định" + }, + "errorPage": { + "message": "Không tải được phím tắt: {}", + "howToFix": "Vui lòng thử lại. Nếu sự cố vẫn tiếp diễn, vui lòng liên hệ trên GitHub." + }, + "resetDialog": { + "title": "Đặt lại phím tắt", + "description": "Thao tác này sẽ khôi phục tất cả các phím tắt của bạn về mặc định, bạn không thể hoàn tác thao tác này sau đó, bạn có chắc chắn muốn tiếp tục không?", + "buttonLabel": "Cài lại" + }, + "conflictDialog": { + "title": "{} hiện đang được sử dụng", + "descriptionPrefix": "Phím tắt này hiện đang được sử dụng bởi ", + "descriptionSuffix": ". Nếu bạn thay thế phím tắt này, nó sẽ bị xóa khỏi {}.", + "confirmLabel": "Tiếp tục" + }, + "editTooltip": "Nhấn để bắt đầu chỉnh sửa phím tắt", + "keybindings": { + "toggleToDoList": "Chuyển sang danh sách việc cần làm", + "insertNewParagraphInCodeblock": "Chèn đoạn văn mới", + "pasteInCodeblock": "Dán vào codeblock", + "selectAllCodeblock": "Chọn tất cả", + "indentLineCodeblock": "Chèn hai khoảng trắng vào đầu dòng", + "outdentLineCodeblock": "Xóa hai khoảng trắng ở đầu dòng", + "twoSpacesCursorCodeblock": "Chèn hai khoảng trắng vào con trỏ", + "copy": "Sao chép lựa chọn", + "paste": "Dán vào nội dung", + "cut": "Cắt lựa chọn", + "alignLeft": "Căn chỉnh văn bản sang trái", + "alignCenter": "Căn giữa văn bản", + "alignRight": "Căn chỉnh văn bản bên phải", + "undo": "Hoàn tác", + "redo": "Làm lại", + "convertToParagraph": "Chuyển đổi khối thành đoạn văn", + "backspace": "Xóa bỏ", + "deleteLeftWord": "Xóa từ bên trái", + "deleteLeftSentence": "Xóa câu bên trái", + "delete": "Xóa ký tự bên phải", + "deleteMacOS": "Xóa ký tự bên trái", + "deleteRightWord": "Xóa từ bên phải", + "moveCursorLeft": "Di chuyển con trỏ sang trái", + "moveCursorBeginning": "Di chuyển con trỏ đến đầu", + "moveCursorLeftWord": "Di chuyển con trỏ sang trái một từ", + "moveCursorLeftSelect": "Chọn và di chuyển con trỏ sang trái", + "moveCursorBeginSelect": "Chọn và di chuyển con trỏ đến đầu", + "moveCursorLeftWordSelect": "Chọn và di chuyển con trỏ sang trái một từ", + "moveCursorRight": "Di chuyển con trỏ sang phải", + "moveCursorEnd": "Di chuyển con trỏ đến cuối", + "moveCursorRightWord": "Di chuyển con trỏ sang phải một từ", + "moveCursorRightSelect": "Chọn và di chuyển con trỏ sang phải một", + "moveCursorEndSelect": "Chọn và di chuyển con trỏ đến cuối", + "moveCursorRightWordSelect": "Chọn và di chuyển con trỏ sang phải một từ", + "moveCursorUp": "Di chuyển con trỏ lên", + "moveCursorTopSelect": "Chọn và di chuyển con trỏ lên trên cùng", + "moveCursorTop": "Di chuyển con trỏ lên trên cùng", + "moveCursorUpSelect": "Chọn và di chuyển con trỏ lên", + "moveCursorBottomSelect": "Chọn và di chuyển con trỏ xuống dưới cùng", + "moveCursorBottom": "Di chuyển con trỏ xuống dưới cùng", + "moveCursorDown": "Di chuyển con trỏ xuống", + "moveCursorDownSelect": "Chọn và di chuyển con trỏ xuống", + "home": "Cuộn lên đầu trang", + "end": "Cuộn xuống dưới cùng", + "toggleBold": "Chuyển đổi chữ đậm", + "toggleItalic": "Chuyển đổi nghiêng", + "toggleUnderline": "Chuyển đổi gạch chân", + "toggleStrikethrough": "Chuyển đổi gạch ngang", + "toggleCode": "Chuyển đổi mã trong dòng", + "toggleHighlight": "Chuyển đổi nổi bật", + "showLinkMenu": "Hiển thị menu liên kết", + "openInlineLink": "Mở liên kết nội tuyến", + "openLinks": "Mở tất cả các liên kết đã chọn", + "indent": "thụt lề", + "outdent": "Nhô ra ngoài", + "exit": "Thoát khỏi chỉnh sửa", + "pageUp": "Cuộn lên một trang", + "pageDown": "Cuộn xuống một trang", + "selectAll": "Chọn tất cả", + "pasteWithoutFormatting": "Dán nội dung không định dạng", + "showEmojiPicker": "Hiển thị bộ chọn biểu tượng cảm xúc", + "enterInTableCell": "Thêm ngắt dòng trong bảng", + "leftInTableCell": "Di chuyển sang trái một ô trong bảng", + "rightInTableCell": "Di chuyển sang phải một ô trong bảng", + "upInTableCell": "Di chuyển lên một ô trong bảng", + "downInTableCell": "Di chuyển xuống một ô trong bảng", + "tabInTableCell": "Đi tới ô có sẵn tiếp theo trong bảng", + "shiftTabInTableCell": "Đi đến ô có sẵn trước đó trong bảng", + "backSpaceInTableCell": "Dừng lại ở đầu ô" + }, + "commands": { + "codeBlockNewParagraph": "Chèn một đoạn văn mới bên cạnh khối mã", + "codeBlockIndentLines": "Chèn hai khoảng trắng vào đầu dòng trong khối mã", + "codeBlockOutdentLines": "Xóa hai khoảng trắng ở đầu dòng trong khối mã", + "codeBlockAddTwoSpaces": "Chèn hai khoảng trắng vào vị trí con trỏ trong khối mã", + "codeBlockSelectAll": "Chọn tất cả nội dung bên trong một khối mã", + "codeBlockPasteText": "Dán văn bản vào codeblock", + "textAlignLeft": "Căn chỉnh văn bản sang trái", + "textAlignCenter": "Căn chỉnh văn bản vào giữa", + "textAlignRight": "Căn chỉnh văn bản sang phải" + }, + "couldNotLoadErrorMsg": "Không thể tải phím tắt, hãy thử lại", + "couldNotSaveErrorMsg": "Không thể lưu phím tắt, hãy thử lại" + }, + "aiPage": { + "title": "Cài đặt AI", + "menuLabel": "Cài đặt AI", + "keys": { + "enableAISearchTitle": "Tìm kiếm AI", + "aiSettingsDescription": "Chọn mô hình ưa thích của bạn để hỗ trợ AppFlowy AI. Bây giờ bao gồm GPT 4-o, Claude 3,5, Llama 3.1 và Mistral 7B", + "loginToEnableAIFeature": "Các tính năng AI chỉ được bật sau khi đăng nhập bằng @:appName Cloud. Nếu bạn không có tài khoản @:appName , hãy vào 'Tài khoản của tôi' để đăng ký", + "llmModel": "Mô hình ngôn ngữ", + "llmModelType": "Kiểu mô hình ngôn ngữ", + "downloadLLMPrompt": "Tải xuống {}", + "downloadAppFlowyOfflineAI": "Tải xuống gói AI ngoại tuyến sẽ cho phép AI chạy trên thiết bị của bạn. Bạn có muốn tiếp tục không?", + "downloadLLMPromptDetail": "Tải xuống mô hình cục bộ {} sẽ chiếm tới {} dung lượng lưu trữ. Bạn có muốn tiếp tục không?", + "downloadBigFilePrompt": "Có thể mất khoảng 10 phút để hoàn tất việc tải xuống", + "downloadAIModelButton": "Tải về", + "downloadingModel": "Đang tải xuống", + "localAILoaded": "Mô hình AI cục bộ đã được thêm thành công và sẵn sàng sử dụng", + "localAIStart": "Trò chuyện AI cục bộ đang bắt đầu...", + "localAILoading": "Mô hình trò chuyện AI cục bộ đang tải...", + "localAIStopped": "AI cục bộ đã dừng", + "failToLoadLocalAI": "Không thể khởi động AI cục bộ", + "restartLocalAI": "Khởi động lại AI cục bộ", + "disableLocalAITitle": "Vô hiệu hóa AI cục bộ", + "disableLocalAIDescription": "Bạn có muốn tắt AI cục bộ không?", + "localAIToggleTitle": "Chuyển đổi để bật hoặc tắt AI cục bộ", + "offlineAIInstruction1": "Theo dõi", + "offlineAIInstruction2": "chỉ dẫn", + "offlineAIInstruction3": "để kích hoạt AI ngoại tuyến.", + "offlineAIDownload1": "Nếu bạn chưa tải xuống AppFlowy AI, vui lòng", + "offlineAIDownload2": "tải về", + "offlineAIDownload3": "nó đầu tiên", + "activeOfflineAI": "Tích cực", + "downloadOfflineAI": "Tải về", + "openModelDirectory": "Mở thư mục" + } + }, + "planPage": { + "menuLabel": "Kế hoạch", + "title": "Giá gói", + "planUsage": { + "title": "Tóm tắt sử dụng kế hoạch", + "storageLabel": "Kho", + "storageUsage": "{} của {} GB", + "unlimitedStorageLabel": "Lưu trữ không giới hạn", + "collaboratorsLabel": "Thành viên", + "collaboratorsUsage": "{} của {}", + "aiResponseLabel": "Phản hồi của AI", + "aiResponseUsage": "{} của {}", + "unlimitedAILabel": "Phản hồi không giới hạn", + "proBadge": "Chuyên nghiệp", + "aiMaxBadge": "AI Tối đa", + "aiOnDeviceBadge": "AI trên thiết bị dành cho máy Mac", + "memberProToggle": "Thêm thành viên và AI không giới hạn", + "aiMaxToggle": "AI không giới hạn và quyền truy cập vào các mô hình tiên tiến", + "aiOnDeviceToggle": "AI cục bộ cho sự riêng tư tối đa", + "aiCredit": { + "title": "Thêm @:appName Tín dụng AI", + "price": "{}", + "priceDescription": "cho 1.000 tín dụng", + "purchase": "Mua AI", + "info": "Thêm 1.000 tín dụng Ai cho mỗi không gian làm việc và tích hợp AI tùy chỉnh vào quy trình làm việc của bạn một cách liền mạch để có kết quả thông minh hơn, nhanh hơn với tối đa:", + "infoItemOne": "10.000 phản hồi cho mỗi cơ sở dữ liệu", + "infoItemTwo": "1.000 phản hồi cho mỗi không gian làm việc" + }, + "currentPlan": { + "bannerLabel": "Gói hiện tại", + "freeTitle": "Miễn phí", + "proTitle": "Pro", + "teamTitle": "Nhóm", + "freeInfo": "Hoàn hảo cho cá nhân có tối đa 2 thành viên để sắp xếp mọi thứ", + "proInfo": "Hoàn hảo cho các nhóm vừa và nhỏ có tối đa 10 thành viên.", + "teamInfo": "Hoàn hảo cho tất cả các nhóm làm việc hiệu quả và có tổ chức tốt.", + "upgrade": "Thay đổi gói đăng ký", + "canceledInfo": "Gói đăng ký của bạn đã bị hủy, bạn sẽ được hạ cấp xuống gói Miễn phí vào ngày {}." + }, + "addons": { + "title": "Tiện ích bổ sung", + "addLabel": "Thêm vào", + "activeLabel": "Đã thêm", + "aiMax": { + "title": "AI Max", + "description": "Phản hồi AI không giới hạn được hỗ trợ bởi GPT-4o, Claude 3.5 Sonnet và nhiều hơn nữa", + "price": "{}", + "priceInfo": "cho mỗi người dùng mỗi tháng được thanh toán hàng năm" + }, + "aiOnDevice": { + "title": "AI trên thiết bị dành cho máy Mac", + "description": "Chạy Mistral 7B, LLAMA 3 và nhiều mô hình cục bộ khác trên máy của bạn", + "price": "{}", + "priceInfo": "cho mỗi người dùng mỗi tháng được thanh toán hàng năm", + "recommend": "Khuyến nghị M1 hoặc mới hơn" + } + }, + "deal": { + "bannerLabel": "Khuyến mãi năm mới!", + "title": "Phát triển nhóm của bạn!", + "info": "Nâng cấp và tiết kiệm 10% cho gói Pro và Team! Tăng năng suất làm việc của bạn với các tính năng mới mạnh mẽ bao gồm @:appName AI.", + "viewPlans": "Xem các gói đăng ký" + } + } + }, + "billingPage": { + "menuLabel": "Thanh toán", + "title": "Thanh toán", + "plan": { + "title": "Gói đăng ký", + "freeLabel": "Miễn phí", + "proLabel": "Pro", + "planButtonLabel": "Thay đổi gói đăng ký", + "billingPeriod": "Chu kỳ thanh toán", + "periodButtonLabel": "Chỉnh sửa chu kỳ" + }, + "paymentDetails": { + "title": "Chi tiết thanh toán", + "methodLabel": "Phương thức thanh toán", + "methodButtonLabel": "Phương pháp chỉnh sửa" + }, + "addons": { + "title": "Tiện ích bổ sung", + "addLabel": "Thêm vào", + "removeLabel": "Xoá", + "renewLabel": "Làm mới", + "aiMax": { + "label": "AI Max", + "description": "Mở khóa AI không giới hạn và các mô hình tiên tiến", + "activeDescription": "Hóa đơn tiếp theo phải trả vào ngày {}", + "canceledDescription": "AI Max sẽ có sẵn cho đến {}" + }, + "aiOnDevice": { + "label": "AI trên thiết bị dành cho máy Mac", + "description": "Mở khóa AI không giới hạn trên thiết bị của bạn", + "activeDescription": "Hóa đơn tiếp theo phải trả vào ngày {}", + "canceledDescription": "AI On-device dành cho Mac sẽ khả dụng cho đến {}" + }, + "removeDialog": { + "title": "Xoá {}", + "description": "Bạn có chắc chắn muốn xóa {plan} không? Bạn sẽ mất quyền truy cập vào các tính năng và lợi ích của {plan} ngay lập tức." + } + }, + "currentPeriodBadge": "HIỆN TẠI", + "changePeriod": "Thay đổi chu kỳ", + "planPeriod": "{} chu kỳ", + "monthlyInterval": "Hàng tháng", + "monthlyPriceInfo": "mỗi thành viên được thanh toán hàng tháng", + "annualInterval": "Hàng năm", + "annualPriceInfo": "mỗi thành viên được thanh toán hàng năm" + }, + "comparePlanDialog": { + "title": "So sánh và lựa chọn gói đăng ký", + "planFeatures": "Gói đăng ký\nTính năng", + "current": "Hiện tại", + "actions": { + "upgrade": "Nâng cấp", + "downgrade": "Hạ cấp", + "current": "Hiện tại" + }, + "freePlan": { + "title": "Miễn phí", + "description": "Dành cho cá nhân có tối đa 2 thành viên để tổ chức mọi thứ", + "price": "{}", + "priceInfo": "miễn phí mãi mãi" + }, + "proPlan": { + "title": "Pro", + "description": "Dành cho các nhóm nhỏ để quản lý dự án và kiến thức của nhóm", + "price": "{}", + "priceInfo": "cho mỗi người dùng mỗi tháng\nđược thanh toán hàng năm\n\n{} được thanh toán hàng tháng" + }, + "planLabels": { + "itemOne": "Không gian làm việc", + "itemTwo": "Thành viên", + "itemThree": "Kho", + "itemFour": "Chỉnh sửa thời gian thực", + "itemFive": "Ứng dụng di động", + "itemSix": "Phản hồi của AI", + "tooltipSix": "Trọn đời có nghĩa là số lượng phản hồi không bao giờ được thiết lập lại", + "intelligentSearch": "Tìm kiếm thông minh", + "tooltipSeven": "Cho phép bạn tùy chỉnh một phần URL cho không gian làm việc của bạn" + }, + "freeLabels": { + "itemOne": "tính phí theo không gian làm việc", + "itemTwo": "lên đến 2", + "itemThree": "5 GB", + "itemFour": "Đúng", + "itemFive": "Đúng", + "itemSix": "100 trọn đời", + "intelligentSearch": "Tìm kiếm thông minh" + }, + "proLabels": { + "itemOne": "tính phí theo không gian làm việc", + "itemTwo": "lên đến 10", + "itemThree": "không giới hạn", + "itemFour": "Đúng", + "itemFive": "Đúng", + "itemSix": "không giới hạn", + "intelligentSearch": "Tìm kiếm thông minh" + }, + "paymentSuccess": { + "title": "Bạn hiện đang sử dụng gói {}!", + "description": "Thanh toán của bạn đã được xử lý thành công và gói của bạn đã được nâng cấp lên @:appName {}. Bạn có thể xem chi tiết gói đăng ký của mình trên trang Gói đăng ký" + }, + "downgradeDialog": { + "title": "Bạn có chắc chắn muốn hạ cấp gói đăng ký của mình không?", + "description": "Việc hạ cấp gói đăng ký của bạn sẽ đưa bạn trở lại gói Miễn phí. Các thành viên có thể mất quyền truy cập vào không gian làm việc này và bạn có thể cần giải phóng dung lượng để đáp ứng giới hạn lưu trữ của gói Miễn phí.", + "downgradeLabel": "Hạ cấp gói đăng ký" + } + }, + "cancelSurveyDialog": { + "title": "Rất tiếc khi phải tạm biệt bạn", + "description": "Chúng tôi rất tiếc khi phải tạm biệt bạn. Chúng tôi rất muốn nghe phản hồi của bạn để giúp chúng tôi cải thiện @:appName . Vui lòng dành chút thời gian để trả lời một vài câu hỏi.", + "commonOther": "Khác", + "otherHint": "Viết câu trả lời của bạn ở đây", + "questionOne": { + "question": "Điều gì khiến bạn hủy đăng ký @:appName Pro?", + "answerOne": "Chi phí quá cao", + "answerTwo": "Các tính năng không đáp ứng được kỳ vọng", + "answerThree": "Đã tìm thấy một giải pháp thay thế tốt hơn", + "answerFour": "Không sử dụng đủ để bù đắp chi phí", + "answerFive": "Vấn đề dịch vụ hoặc khó khăn kỹ thuật" + }, + "questionTwo": { + "question": "Bạn có khả năng cân nhắc đăng ký lại @:appName Pro trong tương lai không?", + "answerOne": "Rất có thể", + "answerTwo": "Có khả năng", + "answerThree": "Không chắc chắn", + "answerFour": "Không có khả năng", + "answerFive": "Rất không có khả năng" + }, + "questionThree": { + "question": "Bạn đánh giá cao tính năng Pro nào nhất trong suốt thời gian đăng ký?", + "answerOne": "Sự hợp tác của nhiều người dùng", + "answerTwo": "Lịch sử phiên bản thời gian dài hơn", + "answerThree": "Phản hồi AI không giới hạn", + "answerFour": "Truy cập vào các mô hình AI cục bộ" + }, + "questionFour": { + "question": "Bạn sẽ mô tả trải nghiệm chung của bạn với @:appName như thế nào?", + "answerOne": "Tuyệt", + "answerTwo": "Tốt", + "answerThree": "Trung bình", + "answerFour": "Dưới mức trung bình", + "answerFive": "Không hài lòng" + } + }, + "common": { + "reset": "Đặt lại" + }, "menu": { "appearance": "Giao diện", "language": "Ngôn ngữ", @@ -302,7 +926,7 @@ "logout": "Đăng xuất", "logoutPrompt": "Bạn chắc chắn muốn đăng xuất?", "selfEncryptionLogoutPrompt": "Bạn có chắc chắn bạn muốn thoát? Hãy đảm bảo bạn đã sao chép bí mật mã hóa", - "syncSetting": "Đồng bộ cài đặt", + "syncSetting": "Cài đặt đồng bộ", "cloudSettings": "Cài đặt đám mây", "enableSync": "Bật tính năng đồng bộ", "enableEncrypt": "Mã hoá dữ liệu", @@ -310,11 +934,15 @@ "invalidCloudURLScheme": "Scheme không hợp lệ", "cloudServerType": "Máy chủ đám mây", "cloudServerTypeTip": "Xin lưu ý rằng có thể bạn sẽ bị đăng xuất sau khi chuyển máy chủ đám mây", + "cloudLocal": "Cục bộ", "cloudSupabase": "Supabase", "cloudSupabaseUrl": "Supabase URL", - "cloudSupabaseAnonKey": "Supabase anon key", + "cloudSupabaseUrlCanNotBeEmpty": "Url supabase không được để trống", + "cloudSupabaseAnonKey": "Khoá Supabase anon", "cloudSupabaseAnonKeyCanNotBeEmpty": "Anon key không được để trống nếu url supabase không trống", "cloudAppFlowy": "@:appName Cloud Beta", + "cloudAppFlowySelfHost": "@:appName Cloud Tự lưu trữ", + "appFlowyCloudUrlCanNotBeEmpty": "URL đám mây không được để trống", "clickToCopy": "Bấm để sao chép", "selfHostStart": "Nếu bạn không có máy chủ, vui lòng tham khảo", "selfHostContent": "tài liệu", @@ -324,6 +952,7 @@ "cloudWSURLHint": "Nhập địa chỉ websocket của máy chủ của bạn", "restartApp": "Khởi động lại", "restartAppTip": "Khởi động lại ứng dụng để thay đổi có hiệu lực. Xin lưu ý rằng điều này có thể đăng xuất tài khoản hiện tại của bạn", + "changeServerTip": "Sau khi thay đổi máy chủ, bạn phải nhấp vào nút khởi động lại để những thay đổi có hiệu lực", "enableEncryptPrompt": "Kích hoạt mã hóa để bảo mật dữ liệu của bạn với bí mật này. Lưu trữ nó một cách an toàn; một khi đã bật thì không thể tắt được. Nếu bị mất, dữ liệu của bạn sẽ không thể phục hồi được. Bấm để sao chép", "inputEncryptPrompt": "Vui lòng nhập bí mật mã hóa của bạn cho", "clickToCopySecret": "Bấm để sao chép bí mật", @@ -331,7 +960,9 @@ "configServerGuide": "Sau khi chọn `Bắt đầu nhanh`, điều hướng đến `Cài đặt` rồi đến \"Cài đặt đám mây\" để định cấu hình máy chủ tự lưu trữ của bạn.", "inputTextFieldHint": "Bí mật của bạn", "historicalUserList": "Lịch sử đăng nhập", + "historicalUserListTooltip": "Danh sách này hiển thị các tài khoản ẩn danh của bạn. Bạn có thể nhấp vào một tài khoản để xem thông tin chi tiết của tài khoản đó. Các tài khoản ẩn danh được tạo bằng cách nhấp vào nút 'Bắt đầu'", "openHistoricalUser": "Ấn để mở tài khoản ẩn danh", + "customPathPrompt": "Lưu trữ thư mục dữ liệu @:appName trong thư mục được đồng bộ hóa trên đám mây như Google Drive có thể gây ra rủi ro. Nếu cơ sở dữ liệu trong thư mục này được truy cập hoặc sửa đổi từ nhiều vị trí cùng một lúc, có thể dẫn đến xung đột đồng bộ hóa và hỏng dữ liệu tiềm ẩn", "importAppFlowyData": "Nhập dữ liệu từ thư mục @:appName bên ngoài", "importingAppFlowyDataTip": "Quá trình nhập dữ liệu đang diễn ra. Vui lòng không đóng ứng dụng", "importAppFlowyDataDescription": "Sao chép dữ liệu từ thư mục dữ liệu @:appName bên ngoài và nhập dữ liệu đó vào thư mục dữ liệu @:appName hiện tại", @@ -343,13 +974,58 @@ "enableNotifications": { "label": "Bật thông báo", "hint": "Tắt để ngăn thông báo xuất hiện." + }, + "showNotificationsIcon": { + "label": "Hiển thị biểu tượng thông báo", + "hint": "Tắt để ẩn biểu tượng thông báo ở thanh bên." + }, + "archiveNotifications": { + "allSuccess": "Đã lưu trữ tất cả thông báo thành công", + "success": "Đã lưu trữ thông báo thành công" + }, + "markAsReadNotifications": { + "allSuccess": "Đã đánh dấu tất cả là đã đọc thành công", + "success": "Đã đánh dấu là đã đọc thành công" + }, + "action": { + "markAsRead": "Đánh dấu là đã đọc", + "multipleChoice": "Chọn thêm", + "archive": "Lưu trữ" + }, + "settings": { + "settings": "Cài đặt", + "markAllAsRead": "Đánh dấu tất cả là đã đọc", + "archiveAll": "Lưu trữ tất cả" + }, + "emptyInbox": { + "title": "Chưa có thông báo nào", + "description": "Bạn sẽ được thông báo ở đây về @đề cập" + }, + "emptyUnread": { + "title": "Không có thông báo chưa đọc", + "description": "Bạn đã hiểu hết rồi!" + }, + "emptyArchived": { + "title": "Không có thông báo lưu trữ", + "description": "Bạn chưa lưu trữ bất kỳ thông báo nào" + }, + "tabs": { + "inbox": "Hộp thư đến", + "unread": "Chưa đọc", + "archived": "Đã lưu trữ" + }, + "refreshSuccess": "Thông báo đã được làm mới thành công", + "titles": { + "notifications": "Thông báo", + "reminder": "Lời nhắc nhở" } }, "appearance": { "resetSetting": "Đặt lại cài đặt", "fontFamily": { "label": "Phông chữ", - "search": "Tìm kiếm" + "search": "Tìm kiếm", + "defaultFont": "Hệ thống" }, "themeMode": { "label": "Chế độ Theme", @@ -357,9 +1033,13 @@ "dark": "Chế độ tối", "system": "Thích ứng với hệ thống" }, + "fontScaleFactor": "Hệ số tỷ lệ phông chữ", "documentSettings": { "cursorColor": "Màu con trỏ", "selectionColor": "Màu lựa chọn tài liệu", + "pickColor": "Chọn một màu", + "colorShade": "Màu sắc bóng râm", + "opacity": "Độ mờ đục", "hexEmptyError": "Màu hex không được để trống", "hexLengthError": "Giá trị hex phải dài 6 chữ số", "hexInvalidError": "Giá trị hex không hợp lệ", @@ -393,9 +1073,9 @@ "filePickerDialogTitle": "Chọn tệp .flowy_plugin", "urlUploadFailure": "Không mở được url: {}" }, - "theme": "Theme", + "theme": "Chủ đề", "builtInsLabel": "Theme có sẵn", - "pluginsLabel": "Plugins", + "pluginsLabel": "Các plugin", "dateFormat": { "label": "Định dạng ngày", "local": "Địa phương", @@ -410,19 +1090,47 @@ "twentyFourHour": "Hai mươi bốn tiếng" }, "showNamingDialogWhenCreatingPage": "Hiển thị hộp thoại đặt tên khi tạo trang", + "enableRTLToolbarItems": "Bật các mục thanh công cụ RTL", "members": { "title": "Cài đặt thành viên", "inviteMembers": "Mời thành viên", + "inviteHint": "Mời qua email", "sendInvite": "Gửi lời mời", "copyInviteLink": "Sao chép liên kết mời", "label": "Các thành viên", "user": "Người dùng", "role": "Vai trò", "removeFromWorkspace": "Xóa khỏi Workspace", + "removeFromWorkspaceSuccess": "Xóa khỏi không gian làm việc thành công", + "removeFromWorkspaceFailed": "Xóa khỏi không gian làm việc không thành công", "owner": "Người sở hữu", "guest": "Khách", "member": "Thành viên", - "members": "các thành viên" + "memberHintText": "Một thành viên có thể đọc và chỉnh sửa các trang", + "guestHintText": "Khách có thể đọc, phản hồi, bình luận và chỉnh sửa một số trang nhất định khi được phép.", + "emailInvalidError": "Email không hợp lệ, vui lòng kiểm tra và thử lại", + "emailSent": "Email đã được gửi, vui lòng kiểm tra hộp thư đến", + "members": "các thành viên", + "membersCount": { + "zero": "{} thành viên", + "one": "{} thành viên", + "other": "{} thành viên" + }, + "inviteFailedDialogTitle": "Không gửi được lời mời", + "inviteFailedMemberLimit": "Đã đạt đến giới hạn thành viên, vui lòng nâng cấp để mời thêm thành viên.", + "inviteFailedMemberLimitMobile": "Không gian làm việc của bạn đã đạt đến giới hạn thành viên. Nâng cấp trên Desktop để mở khóa thêm nhiều tính năng.", + "memberLimitExceeded": "Đã đạt đến giới hạn thành viên, vui lòng mời thêm thành viên ", + "memberLimitExceededUpgrade": "nâng cấp", + "memberLimitExceededPro": "Đã đạt đến giới hạn thành viên, nếu bạn cần thêm thành viên hãy liên hệ ", + "memberLimitExceededProContact": "support@appflowy.io", + "failedToAddMember": "Không thêm được thành viên", + "addMemberSuccess": "Thành viên đã được thêm thành công", + "removeMember": "Xóa thành viên", + "areYouSureToRemoveMember": "Bạn có chắc chắn muốn xóa thành viên này không?", + "inviteMemberSuccess": "Lời mời đã được gửi thành công", + "failedToInviteMember": "Không mời được thành viên", + "workspaceMembersError": "Ồ, có gì đó không ổn", + "workspaceMembersErrorDescription": "Chúng tôi không thể tải danh sách thành viên vào lúc này. Vui lòng thử lại sau" } }, "files": { @@ -448,6 +1156,7 @@ "locationDesc": "Chọn tên cho thư mục dữ liệu @:appName của bạn", "browser": "Duyệt", "create": "Tạo", + "set": "Bộ", "folderPath": "Đường dẫn lưu trữ thư mục của bạn", "locationCannotBeEmpty": "Đường dẫn không thể trống", "pathCopiedSnackbar": "Đã sao chép đường dẫn lưu trữ tệp vào khay nhớ tạm!", @@ -458,22 +1167,26 @@ "recoverLocationTooltips": "Đặt lại về thư mục dữ liệu mặc định của @:appName", "exportFileSuccess": "Xuất tập tin thành công!", "exportFileFail": "Xuất tập tin thất bại!", - "export": "Xuất" + "export": "Xuất", + "clearCache": "Xóa bộ nhớ đệm", + "clearCacheDesc": "Nếu bạn gặp sự cố với hình ảnh không tải hoặc phông chữ không hiển thị đúng, hãy thử xóa bộ nhớ đệm. Hành động này sẽ không xóa dữ liệu người dùng của bạn.", + "areYouSureToClearCache": "Bạn có chắc chắn muốn xóa bộ nhớ đệm không?", + "clearCacheSuccess": "Đã xóa bộ nhớ đệm thành công!" }, "user": { "name": "Tên", - "email": "Email", + "email": "E-mail", "tooltipSelectIcon": "Chọn biểu tượng", "selectAnIcon": "Chọn một biểu tượng", "pleaseInputYourOpenAIKey": "vui lòng nhập khóa AI của bạn", - "pleaseInputYourStabilityAIKey": "vui lòng nhập khóa Stability AI của bạn", - "clickToLogout": "Nhấn để đăng xuất" + "clickToLogout": "Nhấn để đăng xuất", + "pleaseInputYourStabilityAIKey": "vui lòng nhập khóa Stability AI của bạn" }, "mobile": { "personalInfo": "Thông tin cá nhân", "username": "Tên người dùng ", "usernameEmptyError": "Tên người dùng không được để trống", - "about": "About", + "about": "Về", "pushNotifications": "Thông báo", "support": "Ủng hộ", "joinDiscord": "Tham gia cùng chúng tôi trên Discord", @@ -509,14 +1222,17 @@ "deleteFilter": "Xóa bộ lọc", "filterBy": "Lọc bằng...", "typeAValue": "Nhập một giá trị...", - "layout": "Layout", - "databaseLayout": "Layout", + "layout": "Bố cục", + "databaseLayout": "Bố cục", + "viewList": "Database Views", "editView": "Chỉnh sửa chế độ xem", "boardSettings": "Cài đặt bảng", "calendarSettings": "Cài đặt lịch", + "createView": "Góc nhìn mới", + "duplicateView": "Xem trùng lặp", "deleteView": "Xóa chế độ xem", - "Properties": "Thuộc tính", - "viewList": "Database Views" + "numberOfVisibleFields": "{} đã hiển thị", + "Properties": "Thuộc tính" }, "textFilter": { "contains": "Chứa", @@ -558,9 +1274,29 @@ "is": "Là", "before": "Trước", "after": "Sau", + "onOrBefore": "Đang diễn ra hoặc trước đó", + "onOrAfter": "Đang bật hoặc sau", "between": "Ở giữa", "empty": "Rỗng", - "notEmpty": "Không rỗng" + "notEmpty": "Không rỗng", + "choicechipPrefix": { + "before": "Trước", + "after": "Sau đó", + "onOrBefore": "Vào hoặc trước", + "onOrAfter": "Vào hoặc sau", + "isEmpty": "Đang trống", + "isNotEmpty": "Không trống" + } + }, + "numberFilter": { + "equal": "Bằng nhau", + "notEqual": "Không bằng", + "lessThan": "Ít hơn", + "greaterThan": "Lớn hơn", + "lessThanOrEqualTo": "Nhỏ hơn hoặc bằng", + "greaterThanOrEqualTo": "Lớn hơn hoặc bằng", + "isEmpty": "Đang trống", + "isNotEmpty": "Không trống" }, "field": { "hide": "Ẩn", @@ -569,14 +1305,23 @@ "insertRight": "Chèn phải", "duplicate": "Nhân bản", "delete": "Xóa", + "wrapCellContent": "Bao quanh văn bản", + "clear": "Xóa tế bào", "textFieldName": "Chữ", "checkboxFieldName": "Hộp kiểm", "dateFieldName": "Ngày", "updatedAtFieldName": "Sửa đổi lần cuối", "createdAtFieldName": "Được tạo vào lúc", "numberFieldName": "Số", + "singleSelectFieldName": "Lựa chọn", + "multiSelectFieldName": "Chọn nhiều", "urlFieldName": "URL", "checklistFieldName": "Danh mục", + "relationFieldName": "Mối quan hệ", + "summaryFieldName": "Tóm tắt AI", + "timeFieldName": "Thời gian", + "translateFieldName": "AI Dịch", + "translateTo": "Dịch sang", "numberFormat": "Định dạng số", "dateFormat": "Định dạng ngày tháng", "includeTime": "Bao gồm thời gian", @@ -605,10 +1350,13 @@ "addOption": "Thêm tùy chọn", "editProperty": "Chỉnh sửa thuộc tính", "newProperty": "Thuộc tính mới", + "openRowDocument": "Mở như một trang", "deleteFieldPromptMessage": "Bạn có chắc không? Thuộc tính này sẽ bị xóa", + "clearFieldPromptMessage": "Bạn có chắc chắn không? Tất cả các ô trong cột này sẽ được làm trống", "newColumn": "Cột mới", "format": "Định dạng", - "reminderOnDateTooltip": "Ô này có lời nhắc được lên lịch" + "reminderOnDateTooltip": "Ô này có lời nhắc được lên lịch", + "optionAlreadyExist": "Tùy chọn đã tồn tại" }, "rowPage": { "newField": "Thêm một trường mới", @@ -622,13 +1370,20 @@ "one": "Ẩn {count} trường ẩn", "many": "Ẩn {count} trường ẩn", "other": "Ẩn {count} trường ẩn" - } + }, + "openAsFullPage": "Mở dưới dạng trang đầy đủ", + "moreRowActions": "Thêm hành động hàng" }, "sort": { "ascending": "Tăng dần", "descending": "Giảm dần", + "by": "Qua", + "empty": "Không có loại hoạt động", + "cannotFindCreatableField": "Không tìm thấy trường phù hợp để sắp xếp theo", "deleteAllSorts": "Xóa tất cả sắp xếp", - "addSort": "Thêm sắp xếp" + "addSort": "Thêm sắp xếp", + "removeSorting": "Bạn có muốn xóa chế độ sắp xếp không?", + "fieldInUse": "Bạn đang sắp xếp theo trường này" }, "row": { "duplicate": "Nhân bản", @@ -639,10 +1394,14 @@ "count": "Số lượng", "newRow": "Hàng mới", "action": "Hành động", + "add": "Nhấp vào thêm vào bên dưới", "drag": "Kéo để di chuyển", + "deleteRowPrompt": "Bạn có chắc chắn muốn xóa hàng này không? Hành động này không thể hoàn tác", + "deleteCardPrompt": "Bạn có chắc chắn muốn xóa thẻ này không? Hành động này không thể hoàn tác", "dragAndClick": "Kéo để di chuyển, nhấp để mở menu", "insertRecordAbove": "Chèn bản ghi ở trên", - "insertRecordBelow": "Chèn bản ghi bên dưới" + "insertRecordBelow": "Chèn bản ghi bên dưới", + "noContent": "Không có nội dung" }, "selectOption": { "create": "Tạo", @@ -660,33 +1419,230 @@ "panelTitle": "Chọn một tùy chọn hoặc tạo mới", "searchOption": "Tìm kiếm một lựa chọn", "searchOrCreateOption": "Tìm kiếm hoặc tạo một tùy chọn...", + "createNew": "Tạo một cái mới", + "orSelectOne": "Hoặc chọn một tùy chọn", + "typeANewOption": "Nhập một tùy chọn mới", "tagName": "Tên thẻ" }, + "checklist": { + "taskHint": "Mô tả nhiệm vụ", + "addNew": "Thêm một nhiệm vụ mới", + "submitNewTask": "Tạo nên", + "hideComplete": "Ẩn các tác vụ đã hoàn thành", + "showComplete": "Hiển thị tất cả các nhiệm vụ" + }, "url": { - "copy": "Sao chép URL" + "launch": "Mở liên kết trong trình duyệt", + "copy": "Sao chép URL", + "textFieldHint": "Nhập một URL", + "copiedNotification": "Đã sao chép vào bảng tạm!" }, - "menuName": "Lưới" + "relation": { + "relatedDatabasePlaceLabel": "Cơ sở dữ liệu liên quan", + "relatedDatabasePlaceholder": "Không có", + "inRelatedDatabase": "TRONG", + "rowSearchTextFieldPlaceholder": "Tìm kiếm", + "noDatabaseSelected": "Chưa chọn cơ sở dữ liệu, vui lòng chọn một cơ sở dữ liệu trước từ danh sách bên dưới:", + "emptySearchResult": "Không tìm thấy hồ sơ nào", + "linkedRowListLabel": "{count} hàng được liên kết", + "unlinkedRowListLabel": "Liên kết một hàng khác" + }, + "menuName": "Lưới", + "referencedGridPrefix": "Xem của", + "calculate": "Tính toán", + "calculationTypeLabel": { + "none": "Không có", + "average": "Trung bình", + "max": "Tối đa", + "median": "Trung vị", + "min": "Tối thiểu", + "sum": "Tổng", + "count": "Đếm", + "countEmpty": "Đếm trống", + "countEmptyShort": "TRỐNG", + "countNonEmpty": "Đếm không trống", + "countNonEmptyShort": "ĐIỀN" + } }, "document": { + "menuName": "Tài liệu", + "date": { + "timeHintTextInTwelveHour": "01:00 Chiều", + "timeHintTextInTwentyFourHour": "13:00" + }, "slashMenu": { "board": { "selectABoardToLinkTo": "Chọn một bảng để liên kết", "createANewBoard": "Tạo một bảng mới" + }, + "grid": { + "selectAGridToLinkTo": "Chọn một lưới để liên kết đến", + "createANewGrid": "Tạo một lưới mới" + }, + "calendar": { + "selectACalendarToLinkTo": "Chọn Lịch để liên kết đến", + "createANewCalendar": "Tạo một Lịch mới" + }, + "document": { + "selectADocumentToLinkTo": "Chọn một Tài liệu để liên kết đến" + }, + "name": { + "text": "Chữ", + "heading1": "Tiêu đề 1", + "heading2": "Tiêu đề 2", + "heading3": "Tiêu đề 3", + "image": "Hình ảnh", + "bulletedList": "Danh sách có dấu đầu dòng", + "numberedList": "Danh sách được đánh số", + "todoList": "Danh sách việc cần làm", + "doc": "Tài liệu", + "linkedDoc": "Liên kết đến trang", + "grid": "Lưới", + "linkedGrid": "Lưới liên kết", + "kanban": "Kanban", + "linkedKanban": "Kanban liên kết", + "calendar": "Lịch", + "linkedCalendar": "Lịch liên kết", + "quote": "Trích dẫn", + "divider": "Bộ chia", + "table": "Bàn", + "callout": "Chú thích", + "outline": "phác thảo", + "mathEquation": "Phương trình toán học", + "code": "Mã số", + "toggleList": "Chuyển đổi danh sách", + "emoji": "Biểu tượng cảm xúc", + "aiWriter": "Nhà văn AI", + "dateOrReminder": "Ngày hoặc nhắc nhở", + "photoGallery": "Thư viện ảnh", + "file": "Tài liệu" } }, + "selectionMenu": { + "outline": "phác thảo", + "codeBlock": "Khối mã" + }, "plugins": { + "referencedBoard": "Hội đồng tham khảo", + "referencedGrid": "Lưới tham chiếu", + "referencedCalendar": "Lịch tham khảo", + "referencedDocument": "Tài liệu tham khảo", + "autoGeneratorMenuItemName": "Nhà văn AI", + "autoGeneratorTitleName": "AI: Yêu cầu AI viết bất cứ điều gì...", + "autoGeneratorLearnMore": "Tìm hiểu thêm", + "autoGeneratorGenerate": "Phát ra", + "autoGeneratorHintText": "Hỏi AI ...", + "autoGeneratorCantGetOpenAIKey": "Không thể lấy được chìa khóa AI", + "autoGeneratorRewrite": "Viết lại", + "smartEdit": "Hỏi AI", "aI": "AI", + "smartEditFixSpelling": "Sửa lỗi chính tả và ngữ pháp", + "warning": "⚠️ Phản hồi của AI có thể không chính xác hoặc gây hiểu lầm.", + "smartEditSummarize": "Tóm tắt", + "smartEditImproveWriting": "Cải thiện khả năng viết", + "smartEditMakeLonger": "Làm dài hơn", + "smartEditCouldNotFetchResult": "Không thể lấy kết quả từ AI", + "smartEditCouldNotFetchKey": "Không thể lấy được khóa AI", + "smartEditDisabled": "Kết nối AI trong Cài đặt", + "appflowyAIEditDisabled": "Đăng nhập để bật tính năng AI", + "discardResponse": "Bạn có muốn loại bỏ phản hồi của AI không?", + "createInlineMathEquation": "Tạo phương trình", + "fonts": "Phông chữ", + "insertDate": "Chèn ngày", + "emoji": "Biểu tượng cảm xúc", + "toggleList": "Chuyển đổi danh sách", + "quoteList": "Danh sách trích dẫn", + "numberedList": "Danh sách được đánh số", + "bulletedList": "Danh sách có dấu đầu dòng", + "todoList": "Danh sách việc cần làm", + "callout": "Chú thích", + "cover": { + "changeCover": "Thay đổi bìa", + "colors": "Màu sắc", + "images": "Hình ảnh", + "clearAll": "Xóa tất cả", + "abstract": "Tóm tắt", + "addCover": "Thêm Bìa", + "addLocalImage": "Thêm hình ảnh cục bộ", + "invalidImageUrl": "URL hình ảnh không hợp lệ", + "failedToAddImageToGallery": "Không thể thêm hình ảnh vào thư viện", + "enterImageUrl": "Nhập URL hình ảnh", + "add": "Thêm vào", + "back": "Quay lại", + "saveToGallery": "Lưu vào thư viện", + "removeIcon": "Xóa biểu tượng", + "pasteImageUrl": "Dán URL hình ảnh", + "or": "HOẶC", + "pickFromFiles": "Chọn từ các tập tin", + "couldNotFetchImage": "Không thể tải hình ảnh", + "imageSavingFailed": "Lưu hình ảnh không thành công", + "addIcon": "Thêm biểu tượng", + "changeIcon": "Thay đổi biểu tượng", + "coverRemoveAlert": "Nó sẽ được gỡ bỏ khỏi trang bìa sau khi bị xóa.", + "alertDialogConfirmation": "Bạn có chắc chắn muốn tiếp tục không?" + }, + "mathEquation": { + "name": "Phương trình toán học", + "addMathEquation": "Thêm một phương trình TeX", + "editMathEquation": "Chỉnh sửa phương trình toán học" + }, "optionAction": { + "click": "Nhấp chuột", + "toOpenMenu": " để mở menu", "delete": "Xóa", + "duplicate": "Nhân bản", + "turnInto": "Biến thành", + "moveUp": "Di chuyển lên", + "moveDown": "Di chuyển xuống", "color": "Màu", "align": "Căn chỉnh", "left": "Trái", "center": "Giữa", "right": "Phải", - "defaultColor": "Mặc định" + "defaultColor": "Mặc định", + "depth": "Độ sâu" + }, + "image": { + "addAnImage": "Thêm hình ảnh", + "copiedToPasteBoard": "Liên kết hình ảnh đã được sao chép vào clipboard", + "addAnImageDesktop": "Thêm một hình ảnh", + "addAnImageMobile": "Nhấp để thêm một hoặc nhiều hình ảnh", + "dropImageToInsert": "Thả hình ảnh để chèn", + "imageUploadFailed": "Tải hình ảnh lên không thành công", + "imageDownloadFailed": "Tải hình ảnh lên không thành công, vui lòng thử lại", + "imageDownloadFailedToken": "Tải lên hình ảnh không thành công do thiếu mã thông báo người dùng, vui lòng thử lại", + "errorCode": "Mã lỗi" + }, + "photoGallery": { + "name": "Thư viện ảnh", + "imageKeyword": "hình ảnh", + "imageGalleryKeyword": "thư viện hình ảnh", + "photoKeyword": "ảnh", + "photoBrowserKeyword": "trình duyệt ảnh", + "galleryKeyword": "phòng trưng bày", + "addImageTooltip": "Thêm hình ảnh", + "changeLayoutTooltip": "Thay đổi bố cục", + "browserLayout": "Trình duyệt", + "gridLayout": "Lưới", + "deleteBlockTooltip": "Xóa toàn bộ thư viện" + }, + "math": { + "copiedToPasteBoard": "Phương trình toán học đã được sao chép vào clipboard" + }, + "urlPreview": { + "copiedToPasteBoard": "Liên kết đã được sao chép vào clipboard", + "convertToLink": "Chuyển đổi thành liên kết nhúng" + }, + "outline": { + "addHeadingToCreateOutline": "Thêm tiêu đề để tạo mục lục.", + "noMatchHeadings": "Không tìm thấy tiêu đề phù hợp." }, "table": { + "addAfter": "Thêm sau", + "addBefore": "Thêm trước", "delete": "Xoá", + "clear": "Xóa nội dung", + "duplicate": "Nhân bản", "bgColor": "Màu nền" }, "contextMenu": { @@ -694,7 +1650,52 @@ "cut": "Cắt", "paste": "Dán" }, - "action": "Hành động" + "action": "Hành động", + "database": { + "selectDataSource": "Chọn nguồn dữ liệu", + "noDataSource": "Không có nguồn dữ liệu", + "selectADataSource": "Chọn nguồn dữ liệu", + "toContinue": "để tiếp tục", + "newDatabase": "Cơ sở dữ liệu mới", + "linkToDatabase": "Liên kết đến Cơ sở dữ liệu" + }, + "date": "Ngày", + "video": { + "label": "Băng hình", + "emptyLabel": "Thêm video", + "placeholder": "Dán liên kết video", + "copiedToPasteBoard": "Liên kết video đã được sao chép vào clipboard", + "insertVideo": "Thêm video", + "invalidVideoUrl": "URL nguồn chưa được hỗ trợ.", + "invalidVideoUrlYouTube": "YouTube hiện chưa được hỗ trợ.", + "supportedFormats": "Các định dạng được hỗ trợ: MP4, WebM, MOV, AVI, FLV, MPEG/M4V, H.264" + }, + "file": { + "name": "Tài liệu", + "uploadTab": "Tải lên", + "networkTab": "Nhúng liên kết", + "placeholderText": "Tải lên hoặc nhúng một tập tin", + "placeholderDragging": "Thả tệp để tải lên", + "dropFileToUpload": "Thả tệp để tải lên", + "fileUploadHint": "Thả một tập tin ở đây để tải lên\nhoặc nhấp để duyệt", + "networkHint": "Dán liên kết tệp", + "networkUrlInvalid": "URL không hợp lệ, vui lòng sửa URL và thử lại", + "networkAction": "Nhúng liên kết tập tin", + "fileTooBigError": "Kích thước tệp quá lớn, vui lòng tải lên tệp có kích thước nhỏ hơn 10MB", + "renameFile": { + "title": "Đổi tên tập tin", + "description": "Nhập tên mới cho tập tin này", + "nameEmptyError": "Tên tệp không được để trống." + }, + "uploadedAt": "Đã tải lên vào {}", + "linkedAt": "Liên kết đã được thêm vào {}" + } + }, + "outlineBlock": { + "placeholder": "Mục lục" + }, + "textBlock": { + "placeholder": "Gõ '/' cho lệnh" }, "title": { "placeholder": "Trống" @@ -709,17 +1710,68 @@ "label": "Đường dẫn đến ảnh", "placeholder": "Nhập đường dẫn đến ảnh" }, + "ai": { + "label": "Tạo hình ảnh từ AI", + "placeholder": "Vui lòng nhập lời nhắc để AI tạo hình ảnh" + }, + "stability_ai": { + "label": "Tạo hình ảnh từ Stability AI", + "placeholder": "Vui lòng nhập lời nhắc cho Stability AI để tạo hình ảnh" + }, + "support": "Giới hạn kích thước hình ảnh là 5MB. Định dạng được hỗ trợ: JPEG, PNG, GIF, SVG", "error": { - "invalidImage": "Ảnh không hợp lệ" + "invalidImage": "Ảnh không hợp lệ", + "invalidImageSize": "Kích thước hình ảnh phải nhỏ hơn 5MB", + "invalidImageFormat": "Định dạng hình ảnh không được hỗ trợ. Các định dạng được hỗ trợ: JPEG, PNG, JPG, GIF, SVG, WEBP", + "invalidImageUrl": "URL hình ảnh không hợp lệ", + "noImage": "Không có tập tin hoặc thư mục như vậy", + "multipleImagesFailed": "Một hoặc nhiều hình ảnh không tải lên được, vui lòng thử lại" + }, + "embedLink": { + "label": "Nhúng liên kết", + "placeholder": "Dán hoặc nhập liên kết hình ảnh" + }, + "unsplash": { + "label": "Bỏ qua" + }, + "searchForAnImage": "Tìm kiếm hình ảnh", + "pleaseInputYourOpenAIKey": "vui lòng nhập khóa AI của bạn vào trang Cài đặt", + "saveImageToGallery": "Lưu hình ảnh", + "failedToAddImageToGallery": "Không thể thêm hình ảnh vào thư viện", + "successToAddImageToGallery": "Hình ảnh đã được thêm vào thư viện thành công", + "unableToLoadImage": "Không thể tải hình ảnh", + "maximumImageSize": "Kích thước hình ảnh tải lên được hỗ trợ tối đa là 10MB", + "uploadImageErrorImageSizeTooBig": "Kích thước hình ảnh phải nhỏ hơn 10MB", + "imageIsUploading": "Hình ảnh đang được tải lên", + "openFullScreen": "Mở toàn màn hình", + "interactiveViewer": { + "toolbar": { + "previousImageTooltip": "Hình ảnh trước đó", + "nextImageTooltip": "Hình ảnh tiếp theo", + "zoomOutTooltip": "Thu nhỏ", + "zoomInTooltip": "Phóng to", + "changeZoomLevelTooltip": "Thay đổi mức độ thu phóng", + "openLocalImage": "Mở hình ảnh", + "downloadImage": "Tải xuống hình ảnh", + "closeViewer": "Đóng trình xem tương tác", + "scalePercentage": "{}%", + "deleteImageTooltip": "Xóa hình ảnh" + } } }, "codeBlock": { "language": { "label": "Ngôn ngữ", - "placeholder": "Chọn ngôn ngữ" - } + "placeholder": "Chọn ngôn ngữ", + "auto": "Tự động" + }, + "copyTooltip": "Sao chép", + "searchLanguageHint": "Tìm kiếm một ngôn ngữ", + "codeCopiedSnackbar": "Đã sao chép mã vào bảng tạm!" }, "inlineLink": { + "placeholder": "Dán hoặc nhập liên kết", + "openInNewTab": "Mở trong tab mới", "copyLink": "Sao chép liên kết", "removeLink": "Xóa liên kết", "url": { @@ -727,50 +1779,141 @@ "placeholder": "Nhập URL liên kết" }, "title": { - "label": "Tiêu đề liên kết" + "label": "Tiêu đề liên kết", + "placeholder": "Nhập tiêu đề liên kết" } }, + "mention": { + "placeholder": "Nhắc đến một người, một trang hoặc một ngày...", + "page": { + "label": "Liên kết đến trang", + "tooltip": "Nhấp để mở trang" + }, + "deleted": "Đã xóa", + "deletedContent": "Nội dung này không tồn tại hoặc đã bị xóa", + "noAccess": "Không có quyền truy cập" + }, "toolbar": { "resetToDefaultFont": "Chỉnh về mặc định" + }, + "errorBlock": { + "theBlockIsNotSupported": "Không thể phân tích nội dung khối", + "clickToCopyTheBlockContent": "Nhấp để sao chép nội dung khối", + "blockContentHasBeenCopied": "Nội dung khối đã được sao chép." + }, + "mobilePageSelector": { + "title": "Chọn trang", + "failedToLoad": "Không tải được danh sách trang", + "noPagesFound": "Không tìm thấy trang nào" } }, "board": { "column": { "createNewCard": "Mới", + "renameGroupTooltip": "Nhấn để đổi tên nhóm", + "createNewColumn": "Thêm một nhóm mới", + "addToColumnTopTooltip": "Thêm một thẻ mới ở trên cùng", + "addToColumnBottomTooltip": "Thêm một thẻ mới ở phía dưới", "renameColumn": "Đổi tên", "hideColumn": "Ẩn", - "deleteColumn": "Xoá" + "newGroup": "Nhóm mới", + "deleteColumn": "Xoá", + "deleteColumnConfirmation": "Thao tác này sẽ xóa nhóm này và tất cả các thẻ trong đó.\nBạn có chắc chắn muốn tiếp tục không?" + }, + "hiddenGroupSection": { + "sectionTitle": "Nhóm ẩn", + "collapseTooltip": "Ẩn các nhóm ẩn", + "expandTooltip": "Xem các nhóm ẩn" }, + "cardDetail": "Chi tiết thẻ", + "cardActions": "Hành động thẻ", + "cardDuplicated": "Thẻ đã được sao chép", + "cardDeleted": "Thẻ đã bị xóa", + "showOnCard": "Hiển thị trên chi tiết thẻ", + "setting": "Cài đặt", + "propertyName": "Tên tài sản", "menuName": "Bảng", + "showUngrouped": "Hiển thị các mục chưa nhóm", + "ungroupedButtonText": "Không nhóm", + "ungroupedButtonTooltip": "Chứa các thẻ không thuộc bất kỳ nhóm nào", + "ungroupedItemsTitle": "Nhấp để thêm vào bảng", + "groupBy": "Nhóm theo", + "groupCondition": "Điều kiện nhóm", + "referencedBoardPrefix": "Xem của", + "notesTooltip": "Ghi chú bên trong", "mobile": { + "editURL": "Chỉnh sửa URL", "showGroup": "Hiển thị nhóm", "showGroupContent": "Bạn có chắc chắn muốn hiển thị nhóm này trên bảng không?", "failedToLoad": "Không tải được chế độ xem bảng" - } + }, + "dateCondition": { + "weekOf": "Tuần của {} - {}", + "today": "Hôm nay", + "yesterday": "Hôm qua", + "tomorrow": "Ngày mai", + "lastSevenDays": "7 ngày qua", + "nextSevenDays": "7 ngày tiếp theo", + "lastThirtyDays": "30 ngày qua", + "nextThirtyDays": "30 ngày tiếp theo" + }, + "noGroup": "Không có nhóm theo tài sản", + "noGroupDesc": "Các chế độ xem bảng yêu cầu một thuộc tính để nhóm theo để hiển thị" }, "calendar": { "menuName": "Lịch", "defaultNewCalendarTitle": "Trống", + "newEventButtonTooltip": "Thêm sự kiện mới", "navigation": { "today": "Hôm nay", + "jumpToday": "Nhảy tới Hôm nay", "previousMonth": "Tháng trước", - "nextMonth": "Tháng sau" + "nextMonth": "Tháng sau", + "views": { + "day": "Ngày", + "week": "Tuần", + "month": "Tháng", + "year": "Năm" + } + }, + "mobileEventScreen": { + "emptyTitle": "Chưa có sự kiện nào", + "emptyBody": "Nhấn nút dấu cộng để tạo sự kiện vào ngày này." }, "settings": { "showWeekNumbers": "Hiện thứ tự của tuần", "showWeekends": "Hiện cuối tuần", "firstDayOfWeek": "Ngày bắt đầu trong tuần", + "layoutDateField": "Bố trí lịch theo", + "changeLayoutDateField": "Thay đổi trường bố trí", "noDateTitle": "Không có ngày", - "clickToAdd": "Ấn để thêm lịch" - } + "noDateHint": { + "zero": "Các sự kiện không theo lịch trình sẽ hiển thị ở đây", + "one": "{count} sự kiện không theo lịch trình", + "other": "{count} sự kiện không theo lịch trình" + }, + "unscheduledEventsTitle": "Sự kiện không theo lịch trình", + "clickToAdd": "Ấn để thêm lịch", + "name": "Cài đặt lịch", + "clickToOpen": "Nhấp để mở hồ sơ" + }, + "referencedCalendarPrefix": "Xem của", + "quickJumpYear": "Nhảy tới", + "duplicateEvent": "Sự kiện trùng lặp" }, "errorDialog": { "title": "Lỗi của @:appName", "howToFixFallback": "Chúng tôi xin lỗi vì sự cố này! Vui lòng mở sự cố trên GitHub để báo lỗi.", + "howToFixFallbackHint1": "Chúng tôi xin lỗi vì sự bất tiện này! Gửi một vấn đề trên ", + "howToFixFallbackHint2": " trang mô tả lỗi của bạn.", "github": "Xem trên GitHub" }, "search": { - "label": "Tìm kiếm" + "label": "Tìm kiếm", + "sidebarSearchIcon": "Tìm kiếm và nhanh chóng chuyển đến một trang", + "placeholder": { + "actions": "Hành động tìm kiếm..." + } }, "message": { "copy": { @@ -797,12 +1940,16 @@ "gray": "Xám" }, "emoji": { + "emojiTab": "Biểu tượng cảm xúc", "search": "Tìm kiếm biểu tượng", + "noRecent": "Không có biểu tượng cảm xúc gần đây", "noEmojiFound": "Không tìm thấy biểu tượng", "filter": "Bộ lọc", "random": "Ngẫu nhiên", + "selectSkinTone": "Chọn tông màu da", "remove": "Loại bỏ biểu tượng", "categories": { + "smileys": "Biểu tượng mặt cười & Cảm xúc", "people": "Con người và cơ thể", "animals": "Động vật và thiên nhiên", "food": "Đồ ăn và thức uống", @@ -811,21 +1958,58 @@ "objects": "Vật thể", "symbols": "Biểu tượng", "flags": "Cờ", - "nature": "Thiên nhiên", + "nature": "Tự nhiên", "frequentlyUsed": "Thường dùng" }, "skinTone": { - "default": "Mặc định" - } + "default": "Mặc định", + "light": "Sáng", + "mediumLight": "Sáng-Vừa", + "medium": "Vừa", + "mediumDark": "Tối-Vừa", + "dark": "Tối" + }, + "openSourceIconsFrom": "Biểu tượng nguồn mở từ" }, "inlineActions": { "noResults": "Không có kết quả", + "recentPages": "Các trang gần đây", + "pageReference": "Trang tham khảo", + "docReference": "Tài liệu tham khảo", + "boardReference": "Tham khảo bảng", + "calReference": "Tham khảo lịch", + "gridReference": "Tham chiếu lưới", "date": "Ngày", "reminder": { "groupTitle": "Lời nhắc", "shortKeyword": "nhắc nhở" } }, + "datePicker": { + "dateTimeFormatTooltip": "Thay đổi định dạng ngày và giờ trong cài đặt", + "dateFormat": "Định dạng ngày tháng", + "includeTime": "Bao gồm thời gian", + "isRange": "Ngày kết thúc", + "timeFormat": "Định dạng thời gian", + "clearDate": "Ngày xóa", + "reminderLabel": "Lời nhắc nhở", + "selectReminder": "Chọn lời nhắc", + "reminderOptions": { + "none": "Không có", + "atTimeOfEvent": "Thời gian diễn ra sự kiện", + "fiveMinsBefore": "5 phút trước", + "tenMinsBefore": "10 phút trước", + "fifteenMinsBefore": "15 phút trước", + "thirtyMinsBefore": "30 phút trước", + "oneHourBefore": "1 giờ trước", + "twoHoursBefore": "2 giờ trước", + "onDayOfEvent": "Vào ngày diễn ra sự kiện", + "oneDayBefore": "1 ngày trước", + "twoDaysBefore": "2 ngày trước", + "oneWeekBefore": "1 tuần trước", + "custom": "Phong tục" + } + }, "relativeDates": { "yesterday": "Hôm qua", "today": "Hôm nay", @@ -834,18 +2018,35 @@ }, "notificationHub": { "title": "Thông báo", + "mobile": { + "title": "Cập nhật" + }, + "emptyTitle": "Đã cập nhật đầy đủ!", + "emptyBody": "Không có thông báo hoặc hành động nào đang chờ xử lý. Hãy tận hưởng sự bình yên.", + "tabs": { + "inbox": "Hộp thư đến", + "upcoming": "Sắp tới" + }, "actions": { + "markAllRead": "Đánh dấu tất cả là đã đọc", + "showAll": "Tất cả", "showUnreads": "Chưa đọc" }, "filters": { "ascending": "Tăng dần", - "descending": "Giảm dần" + "descending": "Giảm dần", + "groupByDate": "Nhóm theo ngày", + "showUnreadsOnly": "Chỉ hiển thị những tin chưa đọc", + "resetToDefault": "Đặt lại về mặc định" }, "empty": "Không có gì ở đây!" }, "reminderNotification": { "title": "Lời nhắc", - "tooltipDelete": "Xoá" + "message": "Hãy nhớ kiểm tra điều này trước khi bạn quên nhé!", + "tooltipDelete": "Xoá", + "tooltipMarkRead": "Đánh dấu là đã đọc", + "tooltipMarkUnread": "Đánh dấu là chưa đọc" }, "findAndReplace": { "find": "Tìm kiếm", @@ -854,18 +2055,459 @@ "close": "Đóng", "replace": "Thay thế", "replaceAll": "Thay thế tất cả", - "noResult": "Không thấy kết quả" + "noResult": "Không thấy kết quả", + "caseSensitive": "Phân biệt chữ hoa chữ thường", + "searchMore": "Tìm kiếm để tìm thêm kết quả" + }, + "error": { + "weAreSorry": "Chúng tôi xin lỗi", + "loadingViewError": "Chúng tôi đang gặp sự cố khi tải chế độ xem này. Vui lòng kiểm tra kết nối internet, làm mới ứng dụng và đừng ngần ngại liên hệ với nhóm nếu sự cố vẫn tiếp diễn.", + "syncError": "Dữ liệu không được đồng bộ từ thiết bị khác", + "syncErrorHint": "Vui lòng mở lại trang này trên thiết bị mà bạn đã chỉnh sửa lần cuối, sau đó mở lại trên thiết bị hiện tại.", + "clickToCopy": "Nhấp để sao chép mã lỗi" }, "editor": { + "bold": "In đậm", + "bulletedList": "Danh sách có dấu đầu dòng", + "bulletedListShortForm": "Có dấu đầu dòng", + "checkbox": "Đánh dấu", + "embedCode": "Mã nhúng", + "heading1": "H1", + "heading2": "H2", + "heading3": "H3", + "highlight": "Điểm nổi bật", + "color": "Màu sắc", + "image": "Hình ảnh", + "date": "Ngày", + "page": "Trang", + "italic": "nghiêng", + "link": "Liên kết", + "numberedList": "Danh sách được đánh số", + "numberedListShortForm": "Đã đánh số", + "quote": "Trích dẫn", + "strikethrough": "gạch xuyên", + "text": "Chữ", + "underline": "Gạch chân", + "fontColorDefault": "Mặc định", + "fontColorGray": "Xám", + "fontColorBrown": "Màu nâu", + "fontColorOrange": "Quả cam", + "fontColorYellow": "Màu vàng", + "fontColorGreen": "Màu xanh lá", + "fontColorBlue": "Màu xanh da trời", + "fontColorPurple": "Màu tím", + "fontColorPink": "Hồng", + "fontColorRed": "Màu đỏ", + "backgroundColorDefault": "Nền mặc định", + "backgroundColorGray": "Nền xám", + "backgroundColorBrown": "Nền màu nâu", + "backgroundColorOrange": "Nền màu cam", + "backgroundColorYellow": "Nền vàng", + "backgroundColorGreen": "Nền xanh", + "backgroundColorBlue": "Nền xanh", + "backgroundColorPurple": "Nền màu tím", + "backgroundColorPink": "Nền màu hồng", + "backgroundColorRed": "Nền đỏ", + "backgroundColorLime": "Nền vôi", + "backgroundColorAqua": "Nền màu nước", + "done": "Xong", + "cancel": "Hủy bỏ", + "tint1": "Màu 1", + "tint2": "Màu 2", + "tint3": "Màu 3", + "tint4": "Màu 4", + "tint5": "Màu 5", + "tint6": "Màu 6", + "tint7": "Màu 7", + "tint8": "Màu 8", + "tint9": "Màu 9", + "lightLightTint1": "Màu tím", + "lightLightTint2": "Hồng", + "lightLightTint3": "Hồng nhạt", + "lightLightTint4": "Quả cam", + "lightLightTint5": "Màu vàng", + "lightLightTint6": "Chanh xanh", + "lightLightTint7": "Màu xanh lá", + "lightLightTint8": "Nước", + "lightLightTint9": "Màu xanh da trời", + "urlHint": "Địa chỉ URL", + "mobileHeading1": "Tiêu đề 1", + "mobileHeading2": "Tiêu đề 2", + "mobileHeading3": "Tiêu đề 3", + "textColor": "Màu chữ", + "backgroundColor": "Màu nền", + "addYourLink": "Thêm liên kết của bạn", + "openLink": "Mở liên kết", + "copyLink": "Sao chép liên kết", + "removeLink": "Xóa liên kết", + "editLink": "Chỉnh sửa liên kết", + "linkText": "Chữ", + "linkTextHint": "Vui lòng nhập văn bản", + "linkAddressHint": "Vui lòng nhập URL", + "highlightColor": "Màu sắc nổi bật", + "clearHighlightColor": "Xóa màu nổi bật", + "customColor": "Màu tùy chỉnh", + "hexValue": "Giá trị hex", + "opacity": "Độ mờ đục", + "resetToDefaultColor": "Đặt lại màu mặc định", + "ltr": "LTR", + "rtl": "RTL", + "auto": "Tự động", "cut": "Cắt", "copy": "Sao chép", "paste": "Dán", + "find": "Tìm thấy", + "select": "Lựa chọn", + "selectAll": "Chọn tất cả", + "previousMatch": "Trùng khớp trước đó", + "nextMatch": "Trùng khớp tiếp theo", + "closeFind": "Đóng", + "replace": "Thay thế", + "replaceAll": "Thay thế tất cả", + "regex": "Biểu thức chính quy", + "caseSensitive": "Phân biệt chữ hoa chữ thường", + "uploadImage": "Tải lên hình ảnh", + "urlImage": "Hình ảnh URL", + "incorrectLink": "Liên kết không đúng", + "upload": "Tải lên", + "chooseImage": "Chọn một hình ảnh", + "loading": "Đang tải", + "imageLoadFailed": "Tải hình ảnh không thành công", + "divider": "Bộ chia", + "table": "Bàn", + "colAddBefore": "Thêm trước", + "rowAddBefore": "Thêm trước", + "colAddAfter": "Thêm sau", + "rowAddAfter": "Thêm sau", "colRemove": "Xoá", - "rowRemove": "Xoá" + "rowRemove": "Xoá", + "colDuplicate": "Nhân bản", + "rowDuplicate": "Nhân bản", + "colClear": "Xóa nội dung", + "rowClear": "Xóa nội dung", + "slashPlaceHolder": "Nhập '/' để chèn một khối hoặc bắt đầu nhập", + "typeSomething": "Hãy nhập gì đó...", + "toggleListShortForm": "Chuyển đổi", + "quoteListShortForm": "Trích dẫn", + "mathEquationShortForm": "Công thức", + "codeBlockShortForm": "Mã" + }, + "favorite": { + "noFavorite": "Không có trang yêu thích", + "noFavoriteHintText": "Vuốt trang sang trái để thêm vào mục yêu thích của bạn", + "removeFromSidebar": "Xóa khỏi thanh bên", + "addToSidebar": "Ghim vào thanh bên" + }, + "cardDetails": { + "notesPlaceholder": "Nhập / để chèn một khối hoặc bắt đầu nhập" + }, + "blockPlaceholders": { + "todoList": "Việc cần làm", + "bulletList": "Danh sách", + "numberList": "Danh sách", + "quote": "Trích dẫn", + "heading": "Tiêu đề {}" }, "titleBar": { + "pageIcon": "Biểu tượng trang", "language": "Ngôn ngữ", "font": "Phông chữ", - "date": "Ngày" + "actions": "Hành động", + "date": "Ngày", + "addField": "Thêm trường", + "userIcon": "Biểu tượng người dùng" + }, + "noLogFiles": "Không có tệp nhật ký nào", + "newSettings": { + "myAccount": { + "title": "Tài khoản của tôi", + "subtitle": "Tùy chỉnh hồ sơ, quản lý bảo mật tài khoản, mở khóa AI hoặc đăng nhập vào tài khoản của bạn.", + "profileLabel": "Tên tài khoản & Ảnh đại diện", + "profileNamePlaceholder": "Nhập tên của bạn", + "accountSecurity": "Bảo mật tài khoản", + "2FA": "Xác thực 2 bước", + "aiKeys": "Phím AI", + "accountLogin": "Đăng nhập tài khoản", + "updateNameError": "Không cập nhật được tên", + "updateIconError": "Không cập nhật được biểu tượng", + "deleteAccount": { + "title": "Xóa tài khoản", + "subtitle": "Xóa vĩnh viễn tài khoản và toàn bộ dữ liệu của bạn.", + "deleteMyAccount": "Xóa tài khoản của tôi", + "dialogTitle": "Xóa tài khoản", + "dialogContent1": "Bạn có chắc chắn muốn xóa vĩnh viễn tài khoản của mình không?", + "dialogContent2": "Không thể hoàn tác hành động này và sẽ xóa quyền truy cập khỏi mọi không gian làm việc nhóm, xóa toàn bộ tài khoản của bạn, bao gồm cả không gian làm việc riêng tư và xóa bạn khỏi mọi không gian làm việc được chia sẻ." + } + }, + "workplace": { + "name": "Nơi làm việc", + "title": "Cài đặt nơi làm việc", + "subtitle": "Tùy chỉnh giao diện không gian làm việc, chủ đề, phông chữ, bố cục văn bản, ngày, giờ và ngôn ngữ.", + "workplaceName": "Tên nơi làm việc", + "workplaceNamePlaceholder": "Nhập tên nơi làm việc", + "workplaceIcon": "Biểu tượng nơi làm việc", + "workplaceIconSubtitle": "Tải lên hình ảnh hoặc sử dụng biểu tượng cảm xúc cho không gian làm việc của bạn. Biểu tượng sẽ hiển thị trên thanh bên và thông báo của bạn.", + "renameError": "Không đổi được tên nơi làm việc", + "updateIconError": "Không cập nhật được biểu tượng", + "chooseAnIcon": "Chọn một biểu tượng", + "appearance": { + "name": "Vẻ bề ngoài", + "themeMode": { + "auto": "Tự động", + "light": "Ánh sáng", + "dark": "Tối tăm" + }, + "language": "Ngôn ngữ" + } + }, + "syncState": { + "syncing": "Đồng bộ hóa", + "synced": "Đã đồng bộ", + "noNetworkConnected": "Không có kết nối mạng" + } + }, + "pageStyle": { + "title": "Kiểu trang", + "layout": "Cách trình bày", + "coverImage": "Ảnh bìa", + "pageIcon": "Biểu tượng trang", + "colors": "Màu sắc", + "gradient": "Độ dốc", + "backgroundImage": "Hình nền", + "presets": "Cài đặt trước", + "photo": "Ảnh", + "unsplash": "Bỏ qua", + "pageCover": "Trang bìa", + "none": "Không có", + "photoPermissionDescription": "Cho phép truy cập vào thư viện ảnh để tải ảnh lên.", + "openSettings": "Mở Cài đặt", + "photoPermissionTitle": "@:appName muốn truy cập vào thư viện ảnh của bạn", + "doNotAllow": "Không cho phép", + "image": "Hình ảnh" + }, + "commandPalette": { + "placeholder": "Nhập để tìm kiếm...", + "bestMatches": "Trận đấu hay nhất", + "recentHistory": "Lịch sử gần đây", + "navigateHint": "để điều hướng", + "loadingTooltip": "Chúng tôi đang tìm kiếm kết quả...", + "betaLabel": "BETA", + "betaTooltip": "Hiện tại chúng tôi chỉ hỗ trợ tìm kiếm các trang và nội dung trong tài liệu", + "fromTrashHint": "Từ rác", + "noResultsHint": "Chúng tôi không tìm thấy những gì bạn đang tìm kiếm, hãy thử tìm kiếm thuật ngữ khác.", + "clearSearchTooltip": "Xóa trường tìm kiếm" + }, + "space": { + "delete": "Xóa bỏ", + "deleteConfirmation": "Xóa bỏ: ", + "deleteConfirmationDescription": "Tất cả các trang trong Không gian này sẽ bị xóa và chuyển vào Thùng rác, và bất kỳ trang nào đã xuất bản sẽ bị hủy xuất bản.", + "rename": "Đổi tên không gian", + "changeIcon": "Thay đổi biểu tượng", + "manage": "Quản lý không gian", + "addNewSpace": "Tạo không gian", + "collapseAllSubPages": "Thu gọn tất cả các trang con", + "createNewSpace": "Tạo không gian mới", + "createSpaceDescription": "Tạo nhiều không gian công cộng và riêng tư để sắp xếp công việc tốt hơn.", + "spaceName": "Tên không gian", + "spaceNamePlaceholder": "ví dụ Marketing, Kỹ thuật, Nhân sự", + "permission": "Sự cho phép", + "publicPermission": "Công cộng", + "publicPermissionDescription": "Tất cả các thành viên không gian làm việc có quyền truy cập đầy đủ", + "privatePermission": "Riêng tư", + "privatePermissionDescription": "Chỉ bạn mới có thể truy cập vào không gian này", + "spaceIconBackground": "Màu nền", + "spaceIcon": "Biểu tượng", + "dangerZone": "Khu vực nguy hiểm", + "unableToDeleteLastSpace": "Không thể xóa khoảng trắng cuối cùng", + "unableToDeleteSpaceNotCreatedByYou": "Không thể xóa các Không gian do người khác tạo", + "enableSpacesForYourWorkspace": "Bật Spaces cho không gian làm việc của bạn", + "title": "Khoảng cách", + "defaultSpaceName": "Tổng quan", + "upgradeSpaceTitle": "Kích hoạt Khoảng cách", + "upgradeSpaceDescription": "Tạo nhiều Không gian công cộng và riêng tư để sắp xếp không gian làm việc của bạn tốt hơn.", + "upgrade": "Cập nhật", + "upgradeYourSpace": "Tạo nhiều khoảng trống", + "quicklySwitch": "Nhanh chóng chuyển sang không gian tiếp theo", + "duplicate": "Không gian trùng lặp", + "movePageToSpace": "Di chuyển trang đến khoảng trống", + "switchSpace": "Chuyển đổi không gian", + "spaceNameCannotBeEmpty": "Tên khoảng trắng không được để trống" + }, + "publish": { + "hasNotBeenPublished": "Trang này chưa được xuất bản", + "reportPage": "Báo cáo trang", + "databaseHasNotBeenPublished": "Việc xuất bản cơ sở dữ liệu hiện chưa được hỗ trợ.", + "createdWith": "Được tạo ra với", + "downloadApp": "Tải AppFlowy", + "copy": { + "codeBlock": "Nội dung của khối mã đã được sao chép vào bảng tạm", + "imageBlock": "Liên kết hình ảnh đã được sao chép vào clipboard", + "mathBlock": "Phương trình toán học đã được sao chép vào clipboard", + "fileBlock": "Liên kết tập tin đã được sao chép vào clipboard" + }, + "containsPublishedPage": "Trang này chứa một hoặc nhiều trang đã xuất bản. Nếu bạn tiếp tục, chúng sẽ bị hủy xuất bản. Bạn có muốn tiếp tục xóa không?", + "publishSuccessfully": "Đã xuất bản thành công", + "unpublishSuccessfully": "Đã hủy xuất bản thành công", + "publishFailed": "Không thể xuất bản", + "unpublishFailed": "Không thể hủy xuất bản", + "noAccessToVisit": "Không thể truy cập vào trang này...", + "createWithAppFlowy": "Tạo trang web với AppFlowy", + "fastWithAI": "Nhanh chóng và dễ dàng với AI.", + "tryItNow": "Thử ngay bây giờ", + "onlyGridViewCanBePublished": "Chỉ có thể xuất bản chế độ xem Lưới", + "database": { + "zero": "Xuất bản {} chế độ xem đã chọn", + "one": "Xuất bản {} chế độ xem đã chọn", + "many": "Xuất bản {} chế độ xem đã chọn", + "other": "Xuất bản {} chế độ xem đã chọn" + }, + "mustSelectPrimaryDatabase": "Phải chọn chế độ xem chính", + "noDatabaseSelected": "Chưa chọn cơ sở dữ liệu, vui lòng chọn ít nhất một cơ sở dữ liệu.", + "unableToDeselectPrimaryDatabase": "Không thể bỏ chọn cơ sở dữ liệu chính", + "saveThisPage": "Lưu trang này", + "duplicateTitle": "Bạn muốn thêm vào đâu?", + "selectWorkspace": "Chọn không gian làm việc", + "addTo": "Thêm vào", + "duplicateSuccessfully": "Tạo bản sao thành công. Bạn muốn xem tài liệu?", + "duplicateSuccessfullyDescription": "Bạn không có ứng dụng? Quá trình tải xuống của bạn sẽ tự động bắt đầu sau khi nhấp vào 'Tải xuống'.", + "downloadIt": "Tải về", + "openApp": "Mở trong ứng dụng", + "duplicateFailed": "Sao chép không thành công", + "membersCount": { + "zero": "Không có thành viên", + "one": "1 thành viên", + "many": "{đếm} thành viên", + "other": "{đếm} thành viên" + } + }, + "web": { + "continue": "Tiếp tục", + "or": "hoặc", + "continueWithGoogle": "Tiếp tục với Google", + "continueWithGithub": "Tiếp tục với GitHub", + "continueWithDiscord": "Tiếp tục với Discord", + "signInAgreement": "Bằng cách nhấp vào \"Tiếp tục\" ở trên, bạn đã đồng ý với AppFlowy", + "and": "và", + "termOfUse": "Điều khoản", + "privacyPolicy": "Chính sách bảo mật", + "signInError": "Lỗi đăng nhập", + "login": "Đăng ký hoặc đăng nhập", + "fileBlock": { + "uploadedAt": "Đã tải lên vào {time}", + "linkedAt": "Liên kết được thêm vào {time}", + "empty": "Tải lên hoặc nhúng một tập tin" + } + }, + "globalComment": { + "comments": "Bình luận", + "addComment": "Thêm bình luận", + "reactedBy": "phản ứng bởi", + "addReaction": "Thêm phản ứng", + "reactedByMore": "và {đếm} người khác", + "showSeconds": { + "one": "1 giây trước", + "other": "{count} giây trước", + "zero": "Vừa xong", + "many": "{count} giây trước" + }, + "showMinutes": { + "one": "1 phút trước", + "other": "{count} phút trước", + "many": "{count} phút trước" + }, + "showHours": { + "one": "1 giờ trước", + "other": "{count} giờ trước", + "many": "{count} giờ trước" + }, + "showDays": { + "one": "1 ngày trước", + "other": "{count} ngày trước", + "many": "{count} ngày trước" + }, + "showMonths": { + "one": "1 tháng trước", + "other": "{count} tháng trước", + "many": "{count} tháng trước" + }, + "showYears": { + "one": "1 năm trước", + "other": "{đếm} năm trước", + "many": "{đếm} năm trước" + }, + "reply": "Hồi đáp", + "deleteComment": "Xóa bình luận", + "youAreNotOwner": "Bạn không phải là chủ sở hữu của bình luận này", + "confirmDeleteDescription": "Bạn có chắc chắn muốn xóa bình luận này không?", + "hasBeenDeleted": "Đã xóa", + "replyingTo": "Trả lời cho", + "noAccessDeleteComment": "Bạn không được phép xóa bình luận này", + "collapse": "Sụp đổ", + "readMore": "Đọc thêm", + "failedToAddComment": "Không thêm được bình luận", + "commentAddedSuccessfully": "Đã thêm bình luận thành công.", + "commentAddedSuccessTip": "Bạn vừa thêm hoặc trả lời bình luận. Bạn có muốn chuyển lên đầu trang để xem các bình luận mới nhất không?" + }, + "template": { + "asTemplate": "Như mẫu", + "name": "Tên mẫu", + "description": "Mô tả mẫu", + "about": "Mẫu Giới Thiệu", + "preview": "Bản xem trước mẫu", + "categories": "Danh mục mẫu", + "isNewTemplate": "PIN vào mẫu mới", + "featured": "PIN vào mục Nổi bật", + "relatedTemplates": "Mẫu liên quan", + "requiredField": "{field} là bắt buộc", + "addCategory": "Thêm \"{category}\"", + "addNewCategory": "Thêm danh mục mới", + "addNewCreator": "Thêm người sáng tạo mới", + "deleteCategory": "Xóa danh mục", + "editCategory": "Chỉnh sửa danh mục", + "editCreator": "Chỉnh sửa người tạo", + "category": { + "name": "Tên danh mục", + "icon": "Biểu tượng danh mục", + "bgColor": "Màu nền danh mục", + "priority": "Ưu tiên danh mục", + "desc": "Mô tả danh mục", + "type": "Loại danh mục", + "icons": "Biểu tượng danh mục", + "colors": "Thể loại Màu sắc", + "byUseCase": "Theo trường hợp sử dụng", + "byFeature": "Theo tính năng", + "deleteCategory": "Xóa danh mục", + "deleteCategoryDescription": "Bạn có chắc chắn muốn xóa danh mục này không?", + "typeToSearch": "Nhập để tìm kiếm theo danh mục..." + }, + "creator": { + "label": "Người tạo mẫu", + "name": "Tên người sáng tạo", + "avatar": "Avatar của người sáng tạo", + "accountLinks": "Liên kết tài khoản người sáng tạo", + "uploadAvatar": "Nhấp để tải lên hình đại diện", + "deleteCreator": "Xóa người tạo", + "deleteCreatorDescription": "Bạn có chắc chắn muốn xóa người sáng tạo này không?", + "typeToSearch": "Nhập để tìm kiếm người sáng tạo..." + }, + "uploadSuccess": "Mẫu đã được tải lên thành công", + "uploadSuccessDescription": "Mẫu của bạn đã được tải lên thành công. Bây giờ bạn có thể xem nó trong thư viện mẫu.", + "viewTemplate": "Xem mẫu", + "deleteTemplate": "Xóa mẫu", + "deleteTemplateDescription": "Bạn có chắc chắn muốn xóa mẫu này không?", + "addRelatedTemplate": "Thêm mẫu liên quan", + "removeRelatedTemplate": "Xóa mẫu liên quan", + "uploadAvatar": "Tải lên hình đại diện", + "searchInCategory": "Tìm kiếm trong {category}", + "label": "Bản mẫu" + }, + "fileDropzone": { + "dropFile": "Nhấp hoặc kéo tệp vào khu vực này để tải lên", + "uploading": "Đang tải lên...", + "uploadFailed": "Tải lên không thành công", + "uploadSuccess": "Tải lên thành công", + "uploadSuccessDescription": "Tệp đã được tải lên thành công", + "uploadFailedDescription": "Tải tệp lên không thành công", + "uploadingDescription": "Tập tin đang được tải lên" } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/vi.json b/frontend/resources/translations/vi.json index 4d1716447aa81..b921c1844e2e7 100644 --- a/frontend/resources/translations/vi.json +++ b/frontend/resources/translations/vi.json @@ -6,4 +6,4 @@ "failedToLoad": "Không tải được chế độ xem bảng" } } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index 6ce16583aacef..3ddfe1ff19a10 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -688,8 +688,8 @@ "tooltipSelectIcon": "选择图标", "selectAnIcon": "选择一个图标", "pleaseInputYourOpenAIKey": "请输入您的 AI 密钥", - "pleaseInputYourStabilityAIKey": "请输入您的 Stability AI 密钥", - "clickToLogout": "点击退出当前用户" + "clickToLogout": "点击退出当前用户", + "pleaseInputYourStabilityAIKey": "请输入您的 Stability AI 密钥" }, "mobile": { "personalInfo": "个人信息", @@ -1173,14 +1173,14 @@ }, "searchForAnImage": "搜索图像", "pleaseInputYourOpenAIKey": "请在设置页面输入您的 AI 密钥", - "pleaseInputYourStabilityAIKey": "请在设置页面输入您的 Stability AI 密钥", "saveImageToGallery": "保存图片", "failedToAddImageToGallery": "无法将图像添加到图库", "successToAddImageToGallery": "图片已成功添加到图库", "unableToLoadImage": "无法加载图像", "maximumImageSize": "支持的最大上传图片大小为 10MB", "uploadImageErrorImageSizeTooBig": "图片大小必须小于 10MB", - "imageIsUploading": "图片正在上传" + "imageIsUploading": "图片正在上传", + "pleaseInputYourStabilityAIKey": "请在设置页面输入您的 Stability AI 密钥" }, "codeBlock": { "language": { @@ -1621,4 +1621,4 @@ "commandPalette": { "placeholder": "输入你要搜索的内容..." } -} \ No newline at end of file +} diff --git a/frontend/resources/translations/zh-TW.json b/frontend/resources/translations/zh-TW.json index 6db8fc7557189..2474758a00701 100644 --- a/frontend/resources/translations/zh-TW.json +++ b/frontend/resources/translations/zh-TW.json @@ -534,8 +534,8 @@ "tooltipSelectIcon": "選擇圖示", "selectAnIcon": "選擇圖示", "pleaseInputYourOpenAIKey": "請輸入您的 AI 金鑰", - "pleaseInputYourStabilityAIKey": "請輸入您的 Stability AI 金鑰", - "clickToLogout": "點選以登出目前使用者" + "clickToLogout": "點選以登出目前使用者", + "pleaseInputYourStabilityAIKey": "請輸入您的 Stability AI 金鑰" }, "mobile": { "personalInfo": "個人資料", @@ -966,14 +966,14 @@ }, "searchForAnImage": "搜尋圖片", "pleaseInputYourOpenAIKey": "請在設定頁面輸入您的 AI 金鑰", - "pleaseInputYourStabilityAIKey": "請在設定頁面輸入您的 Stability AI 金鑰", "saveImageToGallery": "儲存圖片", "failedToAddImageToGallery": "無法將圖片新增到相簿", "successToAddImageToGallery": "圖片已成功新增到相簿", "unableToLoadImage": "無法載入圖片", "maximumImageSize": "支援的最大上傳圖片大小為 10MB", "uploadImageErrorImageSizeTooBig": "圖片大小必須小於 10MB", - "imageIsUploading": "圖片上傳中" + "imageIsUploading": "圖片上傳中", + "pleaseInputYourStabilityAIKey": "請在設定頁面輸入您的 Stability AI 金鑰" }, "codeBlock": { "language": { @@ -1470,4 +1470,4 @@ "betaLabel": "BETA", "betaTooltip": "目前我們只支援搜尋頁面" } -} \ No newline at end of file +} From eefdf96b0092859ddb3386118f7c4711f44ce0e2 Mon Sep 17 00:00:00 2001 From: Antonio Albert <148400502+AntonioAlbt@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:31:15 +0200 Subject: [PATCH 09/60] fix: typo in german translation (#6126) --- frontend/resources/translations/de-DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/resources/translations/de-DE.json b/frontend/resources/translations/de-DE.json index ce6efae28d02d..e5f77af24ba73 100644 --- a/frontend/resources/translations/de-DE.json +++ b/frontend/resources/translations/de-DE.json @@ -1462,7 +1462,7 @@ "tagName": "Tag-Name" }, "checklist": { - "taskHint": "Aufgbenbeschreibbung", + "taskHint": "Aufgabenbeschreibung", "addNew": "Füge eine Aufgabe hinzu", "submitNewTask": "Erstellen", "hideComplete": "Blende abgeschlossene Aufgaben aus", From 8319606cc0e133239cd01641f6800d217b1b3693 Mon Sep 17 00:00:00 2001 From: Abdulraaof <53502942+Abdulraof@users.noreply.github.com> Date: Fri, 30 Aug 2024 20:13:17 +0800 Subject: [PATCH 10/60] chore: updated Arabic translation (#6111) Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> Co-authored-by: Lucas.Xu --- frontend/resources/translations/ar-SA.json | 72 ++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index 997a4b543d765..b50c2e98058bf 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -2,6 +2,7 @@ "appName": "AppFlowy", "defaultUsername": "أنا", "welcomeText": "مرحبًا بك في @: appName", + "welcomeTo": "مرحبا بكم في", "githubStarText": "نجمة على GitHub", "subscribeNewsletterText": "اشترك في النشرة الإخبارية", "letsGoButtonText": "بداية سريعة", @@ -35,15 +36,37 @@ "loginStartWithAnonymous": "ابدأ بجلسة خفية", "continueAnonymousUser": "استمر بجلسة خفية", "buttonText": "تسجيل الدخول", + "signingInText": "جاري تسجيل الدخول...", "forgotPassword": "هل نسيت كلمة السر؟", "emailHint": "بريد إلكتروني", "passwordHint": "كلمة المرور", "dontHaveAnAccount": "ليس لديك حساب؟", + "createAccount": "إنشاء حساب", "repeatPasswordEmptyError": "إعادة كلمة المرور لا يمكن أن تكون فارغة", "unmatchedPasswordError": "تكرار كلمة المرور ليس هو نفسه كلمة المرور", "syncPromptMessage": "قد تستغرق مزامنة البيانات بعض الوقت. من فضلك لا تغلق هذه الصفحة", "or": "أو", + "signInWithGoogle": "استكمال باستخدام Google", + "signInWithGithub": "استكمال باستخدام Github", + "signInWithDiscord": "استكمال باستخدام Discord", + "signInWithApple": "استكمال باستخدام Apple", + "continueAnotherWay": "استكمال بطريقة أخرى", + "signUpWithGoogle": "سجل باستخدام Google", + "signUpWithGithub": "سجل باستخدام Github", + "signUpWithDiscord": "سجل باستخدام Discord", "signInWith": "تسجيل الدخول ب:", + "signInWithEmail": "متابعة باستخدام البريد الإلكتروني", + "signInWithMagicLink": "استكمال", + "signUpWithMagicLink": "سجل باستخدام Magic Link", + "pleaseInputYourEmail": "الرجاء إدخال عنوان بريدك الإلكتروني", + "settings": "إعدادات", + "magicLinkSent": "تم إرسال Magic Link!", + "invalidEmail": "يرجى إدخال عنوان بريد إلكتروني صالح", + "alreadyHaveAnAccount": "هل لديك حساب؟", + "logIn": "تسجيل الدخول", + "generalError": "حدث خطأ ما. يرجى المحاولة مرة أخرى لاحقًا", + "limitRateError": "لأسباب أمنية، يمكنك طلب Magic Link كل 60 ثانية فقط", + "magicLinkSentDescription": "تم إرسال Magic Link إلى بريدك الإلكتروني. انقر على الرابط لإكمال تسجيل الدخول. ستنتهي صلاحية الرابط بعد 5 دقائق.", "LogInWithGoogle": "تسجيل الدخول عبر جوجل", "LogInWithGithub": "تسجيل الدخول مع جيثب", "LogInWithDiscord": "تسجيل الدخول مع ديسكورد", @@ -53,21 +76,50 @@ "chooseWorkspace": "اختر مساحة العمل الخاصة بك", "create": "قم بإنشاء مساحة عمل", "reset": "إعادة تعيين مساحة العمل", + "renameWorkspace": "إعادة تسمية مساحة العمل", "resetWorkspacePrompt": "ستؤدي إعادة تعيين مساحة العمل إلى حذف جميع الصفحات والبيانات الموجودة بداخلها. هل أنت متأكد أنك تريد إعادة تعيين مساحة العمل؟ وبدلاً من ذلك، يمكنك الاتصال بفريق الدعم لاستعادة مساحة العمل", "hint": "مساحة العمل", "notFoundError": "مساحة العمل غير موجودة", "failedToLoad": "هناك خطأ ما! فشل تحميل مساحة العمل. حاول إغلاق أي مثيل مفتوح لـ @:appName وحاول مرة أخرى.", "errorActions": { "reportIssue": "بلغ عن خطأ", + "reportIssueOnGithub": "الإبلاغ عن مشكلة على Github", + "exportLogFiles": "تصدير ملفات السجل", "reachOut": "تواصل مع ديسكورد" - } + }, + "menuTitle": "مساحات العمل", + "deleteWorkspaceHintText": " هل أنت متأكد من أنك تريد حذف مساحة العمل؟ لا يمكن التراجع عن هذا الإجراء، وستختفي أي صفحات قمت بنشرها سابقاً.", + "createSuccess": "تم إنشاء مساحة العمل بنجاح", + "createFailed": "فشل في إنشاء مساحة العمل", + "deleteSuccess": "تم حذف مساحة العمل بنجاح", + "deleteFailed": "فشل في حذف مساحة العمل", + "openSuccess": "تم فتح مساحة العمل بنجاح", + "openFailed": "فشل في فتح مساحة العمل", + "renameSuccess": "تم إعادة تسمية مساحة العمل بنجاح", + "renameFailed": "فشل في إعادة تسمية مساحة العمل", + "updateIconSuccess": "تم تحديث أيقونة مساحة العمل بنجاح", + "updateIconFailed": "فشل تحديث أيقونة مساحة العمل", + "cannotDeleteTheOnlyWorkspace": "لا يمكن حذف مساحة العمل الوحيدة", + "fetchWorkspacesFailed": "فشل في الوصول لمساحات العمل", + "leaveCurrentWorkspace": "مغادرة مساحة العمل", + "leaveCurrentWorkspacePrompt": "هل أنت متأكد أنك تريد مغادرة مساحة العمل الحالية؟" }, "shareAction": { "buttonText": "مشاركه", "workInProgress": "قريباً", "markdown": "Markdown", + "html": "HTML", + "clipboard": "نسخ إلى الحافظة", "csv": "CSV", - "copyLink": "نسخ الرابط" + "copyLink": "نسخ الرابط", + "publishToTheWeb": "نشر على الويب", + "publishToTheWebHint": "إنشاء موقع ويب مع AppFlowy", + "publish": "نشر", + "unPublish": "التراجع عن النشر", + "visitSite": "زيارة الموقع", + "exportAsTab": "تصدير كـ", + "publishTab": "نشر", + "shareTab": "مشاركة" }, "moreAction": { "small": "صغير", @@ -75,7 +127,12 @@ "large": "كبير", "fontSize": "حجم الخط", "import": "استيراد", - "moreOptions": "المزيد من الخيارات" + "moreOptions": "المزيد من الخيارات", + "wordCount": "عدد الكلمات: {}", + "charCount": "عدد الأحرف: {}", + "createdAt": "منشأ: {}", + "deleteView": "يمسح", + "duplicateView": "تكرار" }, "importPanel": { "textAndMarkdown": "نص و Markdown", @@ -93,7 +150,9 @@ "openNewTab": "افتح في علامة تبويب جديدة", "moveTo": "نقل إلى", "addToFavorites": "اضافة الى المفضلة", - "copyLink": "نسخ الرابط" + "copyLink": "نسخ الرابط", + "changeIcon": "تغيير الأيقونة", + "collapseAllPages": "طي جميع الصفحات الفرعية" }, "blankPageTitle": "صفحة فارغة", "newPageText": "صفحة جديدة", @@ -101,6 +160,11 @@ "newGridText": "شبكة جديدة", "newCalendarText": "تقويم جديد", "newBoardText": "سبورة جديدة", + "chat": { + "newChat": "الدردشة بالذكاء الاصطناعي", + "relatedQuestion": "ذات صلة", + "serverUnavailable": "الخدمة غير متاحة مؤقتًا. يرجى المحاولة مرة أخرى لاحقًا." + }, "trash": { "text": "المهملات", "restoreAll": "استعادة الكل", From 0b658bff0bea088296747f9061d364a4c8775e16 Mon Sep 17 00:00:00 2001 From: Yurmin <60743665+Yurmin@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:13:33 +0200 Subject: [PATCH 11/60] chore: update it-IT translations (#6058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update translations with Fink 🐦 * chore: update translations with Fink 🐦 --------- Co-authored-by: Lucas.Xu --- frontend/resources/translations/it-IT.json | 118 +++++++++++++++++++-- 1 file changed, 112 insertions(+), 6 deletions(-) diff --git a/frontend/resources/translations/it-IT.json b/frontend/resources/translations/it-IT.json index 3233c67fd617b..eba8d58bdcae0 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -2,12 +2,14 @@ "appName": "AppFlowy", "defaultUsername": "Me", "welcomeText": "Benvenuto in @:appName", + "welcomeTo": "Benvenuto a", "githubStarText": "Vota su GitHub", "subscribeNewsletterText": "Sottoscrivi la Newsletter", "letsGoButtonText": "Andiamo", "title": "Titolo", "youCanAlso": "Puoi anche", "and": "E", + "failedToOpenUrl": "Apertura URL fallita: {}", "blockActions": { "addBelowTooltip": "Fare clic per aggiungere di seguito", "addAboveCmd": "Alt+clic", @@ -35,15 +37,35 @@ "loginStartWithAnonymous": "Inizia con una sessione anonima", "continueAnonymousUser": "Continua con una sessione anonima", "buttonText": "Accedi", + "signingInText": "Accesso in corso...", "forgotPassword": "Password Dimentica?", "emailHint": "Email", "passwordHint": "Password", "dontHaveAnAccount": "Non hai un account?", + "createAccount": "Crea account", "repeatPasswordEmptyError": "La password ripetuta non può essere vuota", "unmatchedPasswordError": "La password ripetuta non è uguale alla password", "syncPromptMessage": "La sincronizzazione dei dati potrebbe richiedere del tempo. Per favore, non chiudere questa pagina", "or": "O", + "signInWithGoogle": "Continua con Google", + "signInWithGithub": "Continua con Github", + "signInWithDiscord": "Continua con Discord", + "signUpWithGoogle": "Registrati con Google", + "signUpWithGithub": "Registrati con Github", + "signUpWithDiscord": "Registrati con Discord", "signInWith": "Loggati con:", + "signInWithEmail": "Continua con Email", + "signInWithMagicLink": "Continua", + "signUpWithMagicLink": "Registrati con un Link Magico", + "pleaseInputYourEmail": "Per favore, inserisci il tuo indirizzo email", + "settings": "Impostazioni", + "magicLinkSent": "Link Magico inviato!", + "invalidEmail": "Per favore, inserisci un indirizzo email valido", + "alreadyHaveAnAccount": "Hai già un account?", + "logIn": "Accedi", + "generalError": "Qualcosa è andato storto. Per favore, riprova più tardi", + "limitRateError": "Per ragioni di sicurezza, puoi richiedere un link magico ogni 60 secondi", + "magicLinkSentDescription": "Un Link Magico è stato inviato alla tua email. Clicca il link per completare il tuo accesso. Il link scadrà dopo 5 minuti.", "LogInWithGoogle": "Accedi con Google", "LogInWithGithub": "Accedi con Github", "LogInWithDiscord": "Accedi con Discord", @@ -53,6 +75,7 @@ "chooseWorkspace": "Scegli il tuo spazio di lavoro", "create": "Crea spazio di lavoro", "reset": "Ripristina lo spazio di lavoro", + "renameWorkspace": "Rinomina workspace", "resetWorkspacePrompt": "Il ripristino dello spazio di lavoro eliminerà tutte le pagine e i dati al suo interno. Sei sicuro di voler ripristinare lo spazio di lavoro? In alternativa, puoi contattare il team di supporto per ristabilire lo spazio di lavoro", "hint": "spazio di lavoro", "notFoundError": "Spazio di lavoro non trovato", @@ -62,14 +85,39 @@ "reportIssueOnGithub": "Segnalate un problema su Github", "exportLogFiles": "Esporta i file di log", "reachOut": "Contattaci su Discord" - } + }, + "deleteWorkspaceHintText": "Sei sicuro di voler cancellare la workspace? Questa azione non è reversibile, e ogni pagina che hai pubblicato sarà rimossa.", + "createSuccess": "Workspace creata con successo", + "createFailed": "Creazione workspace fallita", + "createLimitExceeded": "Hai raggiunto il numero massimo di workspace permesse per il tuo account. Se hai bisogno di ulteriori workspace per continuare il tuo lavoro, per favore fai richiesta su Github", + "deleteSuccess": "Workspace cancellata con successo", + "deleteFailed": "Cancellazione workspace fallita", + "openSuccess": "Workspace aperta con successo", + "openFailed": "Apertura workspace fallita", + "renameSuccess": "Workspace rinominata con successo", + "renameFailed": "Rinomina workspace fallita", + "updateIconSuccess": "Icona della workspace aggiornata con successo", + "updateIconFailed": "Aggiornamento icona della workspace fallito", + "cannotDeleteTheOnlyWorkspace": "Impossibile cancellare l'unica workspace", + "fetchWorkspacesFailed": "Recupero workspaces fallito", + "leaveCurrentWorkspace": "Lascia workspace", + "leaveCurrentWorkspacePrompt": "Sei sicuro di voler lasciare la workspace corrente?" }, "shareAction": { "buttonText": "Condividi", "workInProgress": "Prossimamente", "markdown": "Markdown", + "clipboard": "Copia", "csv": "CSV", - "copyLink": "Copia Link" + "copyLink": "Copia Link", + "publishToTheWeb": "Pubblica sul Web", + "publishToTheWebHint": "Crea un sito con AppFlowy", + "publish": "Pubblica", + "unPublish": "Annulla la pubblicazione", + "visitSite": "Visita sito", + "exportAsTab": "Esporta come", + "publishTab": "Pubblica", + "shareTab": "Condividi" }, "moreAction": { "small": "piccolo", @@ -80,6 +128,7 @@ "moreOptions": "Più opzioni", "wordCount": "Conteggio parole: {}", "charCount": "Numero di caratteri: {}", + "createdAt": "Creata: {}", "deleteView": "Cancella", "duplicateView": "Duplica" }, @@ -99,7 +148,9 @@ "openNewTab": "Apri in una nuova scheda", "moveTo": "Sposta in", "addToFavorites": "Aggiungi ai preferiti", - "copyLink": "Copia link" + "copyLink": "Copia link", + "changeIcon": "Cambia icona", + "collapseAllPages": "Comprimi le sottopagine" }, "blankPageTitle": "Pagina vuota", "newPageText": "Nuova pagina", @@ -107,6 +158,32 @@ "newGridText": "Nuova griglia", "newCalendarText": "Nuovo calendario", "newBoardText": "Nuova bacheca", + "chat": { + "newChat": "Chat AI", + "inputMessageHint": "Chiedi a @:appName AI", + "inputLocalAIMessageHint": "Chiedi a @:appName Local AI", + "unsupportedCloudPrompt": "Questa funzione è disponibile solo quando si usa @:appName Cloud", + "relatedQuestion": "Correlato", + "serverUnavailable": "Servizio temporaneamente non disponibile. Per favore, riprova più tardi.", + "aiServerUnavailable": "🌈 Uh-oh! 🌈. Un unicorno ha mangiato la nostra risposta. Per favore, riprova!", + "clickToRetry": "Clicca per riprovare", + "regenerateAnswer": "Rigenera", + "question1": "Come usare Kanban per organizzare le attività", + "question2": "Spiega il metodo GTD", + "question3": "Perché usare Rust", + "question4": "Ricetta con cos'è presente nella mia cucina", + "aiMistakePrompt": "Le IA possono fare errori. Controlla le informazioni importanti.", + "chatWithFilePrompt": "Vuoi chattare col file?", + "indexFileSuccess": "Indicizzazione file completata con successo", + "inputActionNoPages": "Nessuna pagina risultante", + "referenceSource": "{} fonte trovata", + "referenceSources": "{} fonti trovate", + "clickToMention": "Clicca per menzionare una pagina", + "uploadFile": "Carica file PDF, MD o TXT con la quale chattare", + "questionTitle": "Idee", + "questionDetail": "Salve {}! Come posso aiutarti oggi?", + "indexingFile": "Indicizzazione {}" + }, "trash": { "text": "Cestino", "restoreAll": "Ripristina Tutto", @@ -125,11 +202,13 @@ "caption": "Questa azione non può essere annullata." }, "mobile": { + "actions": "Azioni del cestino", "empty": "Il cestino è vuoto", "emptyDescription": "Non hai alcun file eliminato", "isDeleted": "è stato cancellato", "isRestored": "è stato ripristinato" - } + }, + "confirmDeleteTitle": "Sei sicuro di voler eliminare questa pagina permanentemente?" }, "deletePagePrompt": { "text": "Questa pagina è nel Cestino", @@ -183,17 +262,44 @@ "dragRow": "Premere a lungo per riordinare la riga", "viewDataBase": "Visualizza banca dati", "referencePage": "Questo {nome} è referenziato", - "addBlockBelow": "Aggiungi un blocco qui sotto" + "addBlockBelow": "Aggiungi un blocco qui sotto", + "aiGenerate": "Genera" }, "sideBar": { "closeSidebar": "Close sidebar", "openSidebar": "Open sidebar", "personal": "Personale", + "private": "Privato", "favorites": "Preferiti", + "clickToHidePrivate": "Clicca per nascondere l'area privata\nLe pagine create qui sono visibili solo a te", + "clickToHideWorkspace": "Clicca per nascondere la workspace\nLe pagine che crei qui sono visibili a ogni membro", "clickToHidePersonal": "Fare clic per nascondere la sezione personale", "clickToHideFavorites": "Fare clic per nascondere la sezione dei preferiti", "addAPage": "Aggiungi una pagina", - "recent": "Recente" + "addAPageToPrivate": "Aggiungi pagina all'area privata", + "addAPageToWorkspace": "Aggiungi pagina alla workspace", + "recent": "Recente", + "today": "Oggi", + "thisWeek": "Questa settimana", + "others": "Preferiti precedenti", + "justNow": "poco fa", + "minutesAgo": "{count} minuti fa", + "lastViewed": "Visto per ultimo", + "favoriteAt": "Aggiunto tra ai preferiti", + "emptyRecent": "Nessun documento recente", + "emptyRecentDescription": "Quando vedrai documenti, appariranno qui per accesso facilitato", + "emptyFavorite": "Nessun documento preferito", + "emptyFavoriteDescription": "Comincia a esplorare e marchia documenti come preferiti. Verranno elencati qui per accesso rapido!", + "removePageFromRecent": "Rimuovere questa pagina da Recenti?", + "removeSuccess": "Rimozione effettuata con successo", + "favoriteSpace": "Preferiti", + "RecentSpace": "Recenti", + "Spaces": "Aree", + "upgradeToPro": "Aggiorna a Pro", + "upgradeToAIMax": "Sblocca AI illimitata", + "storageLimitDialogTitle": "Hai esaurito lo spazio d'archiviazione gratuito. Aggiorna per avere spazio d'archiviazione illimitato!", + "aiResponseLimitTitle": "Hai esaurito le risposte AI gratuite. Aggiorna al Piano Pro per acquistare un add-on AI per avere risposte illimitate", + "aiResponseLimitDialogTitle": "Numero di riposte AI raggiunto" }, "notifications": { "export": { From c20ed8c019dc281690f43fad568ebc54ce323429 Mon Sep 17 00:00:00 2001 From: MIckael Date: Fri, 30 Aug 2024 09:14:04 -0300 Subject: [PATCH 12/60] chore: update Pt-br translations (#6107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update translations with Fink 🐦 * chore: update translations with Fink 🐦 --------- Co-authored-by: Lucas.Xu --- frontend/resources/translations/pt-BR.json | 344 ++++++++++++++++++++- 1 file changed, 337 insertions(+), 7 deletions(-) diff --git a/frontend/resources/translations/pt-BR.json b/frontend/resources/translations/pt-BR.json index abbd35605eab4..034985ec665e0 100644 --- a/frontend/resources/translations/pt-BR.json +++ b/frontend/resources/translations/pt-BR.json @@ -9,6 +9,7 @@ "title": "Título", "youCanAlso": "Você também pode", "and": "e", + "failedToOpenUrl": "Falha ao abrir url: {}", "blockActions": { "addBelowTooltip": "Clique para adicionar abaixo", "addAboveCmd": "Alt+clique", @@ -35,17 +36,39 @@ "loginButtonText": "Conectar-se", "loginStartWithAnonymous": "Iniciar com uma sessão anônima", "continueAnonymousUser": "Continuar em uma sessão anônima", + "anonymous": "Anônimo", "buttonText": "Entre", "signingInText": "Entrando...", "forgotPassword": "Esqueceu sua senha?", "emailHint": "E-mail", "passwordHint": "Senha", "dontHaveAnAccount": "Não possui uma conta?", + "createAccount": "Criar uma conta", "repeatPasswordEmptyError": "Senha não pode estar em branco.", "unmatchedPasswordError": "As senhas não conferem.", "syncPromptMessage": "A sincronização dos dados pode demorar um pouco. Por favor não feche esta página", "or": "OU", + "signInWithGoogle": "Continuar com o Google", + "signInWithGithub": "Continuar com o Github", + "signInWithDiscord": "Continuar com o Discord", + "signInWithApple": "Continuar com a Apple", + "continueAnotherWay": "Continuar de outra forma", + "signUpWithGoogle": "Cadastro com o Google", + "signUpWithGithub": "Cadastro com o Github", + "signUpWithDiscord": "Cadastro com o Discord", "signInWith": "Entrar com:", + "signInWithEmail": "Continuar com e-mail", + "signInWithMagicLink": "Continuar", + "signUpWithMagicLink": "Cadastro com um Link Mágico", + "pleaseInputYourEmail": "Por favor, insira seu endereço de e-mail", + "settings": "Configurações", + "magicLinkSent": "Link Mágico enviado!", + "invalidEmail": "Por favor, insira um endereço de e-mail válido", + "alreadyHaveAnAccount": "Já tem uma conta?", + "logIn": "Entrar", + "generalError": "Algo deu errado. Tente novamente mais tarde.", + "limitRateError": "Por razões de segurança, você só pode solicitar um link mágico a cada 60 segundos", + "magicLinkSentDescription": "Um Link Mágico foi enviado para seu e-mail. Clique no link para concluir seu login. O link expirará após 5 minutos.", "LogInWithGoogle": "Entrar com o Google", "LogInWithGithub": "Entrar com o Github", "LogInWithDiscord": "Entrar com o Discord", @@ -55,21 +78,50 @@ "chooseWorkspace": "Escolha seu espaço de trabalho", "create": "Crie um espaço de trabalho", "reset": "Redefinir espaço de trabalho", + "renameWorkspace": "Renomear espaço de trabalho", "resetWorkspacePrompt": "A redefinição do espaço de trabalho excluirá todas as páginas e dados contidos nele. Tem certeza de que deseja redefinir o espaço de trabalho? Alternativamente, você pode entrar em contato com a equipe de suporte para restaurar o espaço de trabalho", "hint": "Espaço de trabalho", "notFoundError": "Espaço de trabalho não encontrado", "failedToLoad": "Algo deu errado! Falha ao carregar o espaço de trabalho. Tente fechar qualquer instância aberta do @:appName e tente novamente.", "errorActions": { "reportIssue": "Reporte um problema", + "reportIssueOnGithub": "Reportar um problema no Github", + "exportLogFiles": "Exportar arquivos de log", "reachOut": "Entre em contato no Discord" - } + }, + "menuTitle": "Espaços de trabalho", + "deleteWorkspaceHintText": "Tem certeza de que deseja excluir o espaço de trabalho? Esta ação não pode ser desfeita, e quaisquer páginas que você tenha publicado deixarão de estar publicadas.", + "createSuccess": "Espaço de trabalho criado com sucesso", + "createFailed": "Falha ao criar espaço de trabalho", + "createLimitExceeded": "Você atingiu o limite máximo de espaços de trabalho permitido para sua conta. Se precisar de espaços de trabalho adicionais para continuar seu trabalho, solicite no Github", + "deleteSuccess": "Espaço de trabalho excluído com sucesso", + "deleteFailed": "Falha ao excluir o espaço de trabalho", + "openSuccess": "Espaço de trabalho aberto com sucesso", + "openFailed": "Falha ao abrir o espaço de trabalho", + "renameSuccess": "Espaço de trabalho renomeado com sucesso", + "renameFailed": "Falha ao renomear o espaço de trabalho", + "updateIconSuccess": "Ícone do espaço de trabalho atualizado com sucesso", + "updateIconFailed": "Falha ao atualizar ícone do espaço de trabalho", + "cannotDeleteTheOnlyWorkspace": "Não é possível excluir o único espaço de trabalho", + "fetchWorkspacesFailed": "Falha ao buscar espaços de trabalho", + "leaveCurrentWorkspace": "Sair do espaço de trabalho", + "leaveCurrentWorkspacePrompt": "Tem certeza de que deseja sair do espaço de trabalho atual?" }, "shareAction": { "buttonText": "Compartilhar", "workInProgress": "Em breve", "markdown": "Marcador", + "clipboard": "Copiar para área de transferência", "csv": "CSV", - "copyLink": "Copiar link" + "copyLink": "Copiar link", + "publishToTheWeb": "Publicar na Web", + "publishToTheWebHint": "Crie um site com AppFlowy", + "publish": "Publicar", + "unPublish": "Remover publicação", + "visitSite": "Visitar site", + "exportAsTab": "Exportar como", + "publishTab": "Publicar", + "shareTab": "Compartilhar" }, "moreAction": { "small": "pequeno", @@ -77,7 +129,12 @@ "large": "grande", "fontSize": "Tamanho da fonte", "import": "Importar", - "moreOptions": "Mais opções" + "moreOptions": "Mais opções", + "wordCount": "Contagem de palavras: {}", + "charCount": "Contagem de caracteres: {}", + "createdAt": "Criado: {}", + "deleteView": "Excluir", + "duplicateView": "Duplicar" }, "importPanel": { "textAndMarkdown": "Texto e Remarcação", @@ -95,7 +152,9 @@ "openNewTab": "Abrir em uma nova guia", "moveTo": "Mover para", "addToFavorites": "Adicionar aos favoritos", - "copyLink": "Copiar link" + "copyLink": "Copiar link", + "changeIcon": "Alterar ícone", + "collapseAllPages": "Recolher todas as subpáginas" }, "blankPageTitle": "Página em branco", "newPageText": "Nova página", @@ -103,6 +162,32 @@ "newGridText": "Nova grelha", "newCalendarText": "Novo calendário", "newBoardText": "Novo quadro", + "chat": { + "newChat": "Bate-papo com IA", + "inputMessageHint": "Pergunte a IA @:appName", + "inputLocalAIMessageHint": "Pergunte a IA local @:appName", + "unsupportedCloudPrompt": "Este recurso só está disponível ao usar a nuvem @:appName", + "relatedQuestion": "Relacionado", + "serverUnavailable": "Serviço Temporariamente Indisponível. Tente novamente mais tarde.", + "aiServerUnavailable": "🌈 Uh-oh! 🌈. Um unicórnio comeu nossa resposta. Por favor, tente novamente!", + "clickToRetry": "Clique para tentar novamente", + "regenerateAnswer": "Gerar novamente", + "question1": "Como usar Kanban para gerenciar tarefas", + "question2": "Explique o método GTD", + "question3": "Por que usar Rust", + "question4": "Receita com o que tenho na cozinha", + "aiMistakePrompt": "A IA pode cometer erros. Verifique informações importantes.", + "chatWithFilePrompt": "Você quer conversar com o arquivo?", + "indexFileSuccess": "Arquivo indexado com sucesso", + "inputActionNoPages": "Nenhum resultado", + "referenceSource": "{} fonte encontrada", + "referenceSources": "{} fontes encontradas", + "clickToMention": "Clique para mencionar uma página", + "uploadFile": "Carregue arquivos PDFs, md ou txt para conversar", + "questionTitle": "Ideias", + "questionDetail": "Olá {}! Como posso te ajudar hoje?", + "indexingFile": "Indexando {}" + }, "trash": { "text": "Lixeira", "restoreAll": "Restaurar tudo", @@ -126,7 +211,8 @@ "emptyDescription": "Você não tem nenhum arquivo excluído", "isDeleted": "foi deletado", "isRestored": "foi restaurado" - } + }, + "confirmDeleteTitle": "Tem certeza de que deseja excluir esta página permanentemente?" }, "deletePagePrompt": { "text": "Está página está na lixeira", @@ -180,17 +266,53 @@ "dragRow": "Pressione e segure para reordenar a linha", "viewDataBase": "Visualizar banco de dados", "referencePage": "Esta {name} é uma referência", - "addBlockBelow": "Adicione um bloco abaixo" + "addBlockBelow": "Adicione um bloco abaixo", + "aiGenerate": "Gerar" }, "sideBar": { "closeSidebar": "Fechar barra lateral", "openSidebar": "Abrir barra lateral", "personal": "Pessoal", + "private": "Privado", + "workspace": "Espaço de trabalho", "favorites": "Favoritos", + "clickToHidePrivate": "Clique para ocultar o espaço privado\nAs páginas que você criou aqui são visíveis apenas para você", + "clickToHideWorkspace": "Clique para ocultar o espaço de trabalho\nAs páginas que você criou aqui são visíveis para todos os membros", "clickToHidePersonal": "Clique para ocultar a seção pessoal", "clickToHideFavorites": "Clique para ocultar a seção favorita", "addAPage": "Adicionar uma página", - "recent": "Recentes" + "addAPageToPrivate": "Adicionar uma página ao espaço privado", + "addAPageToWorkspace": "Adicionar uma página ao espaço de trabalho", + "recent": "Recentes", + "today": "Hoje", + "thisWeek": "Essa semana", + "others": "Favoritos anteriores", + "justNow": "agora mesmo", + "minutesAgo": "{count} minutos atrás", + "lastViewed": "Última visualização", + "favoriteAt": "Favorito", + "emptyRecent": "Nenhum documento recente", + "emptyRecentDescription": "Conforme você visualiza os documentos, eles aparecerão aqui para fácil recuperação", + "emptyFavorite": "Nenhum documento favorito", + "emptyFavoriteDescription": "Comece a explorar e marque os documentos como favoritos. Eles serão listados aqui para acesso rápido!", + "removePageFromRecent": "Remover esta página dos Recentes?", + "removeSuccess": "Removido com sucesso", + "favoriteSpace": "Favoritos", + "RecentSpace": "Recente", + "Spaces": "Espaços", + "upgradeToPro": "Atualizar para Pro", + "upgradeToAIMax": "Desbloqueie IA ilimitada", + "storageLimitDialogTitle": "Você ficou sem armazenamento gratuito. Atualize para desbloquear armazenamento ilimitado", + "aiResponseLimitTitle": "Você ficou sem respostas de IA gratuitas. Atualize para o Plano Pro ou adquira um complemento de IA para desbloquear respostas ilimitadas", + "aiResponseLimitDialogTitle": "Limite de respostas de IA atingido", + "aiResponseLimit": "Você ficou sem respostas de IA gratuitas.\n\nVá para Configurações -> Plano -> Clique em AI Max ou Plano Pro para obter mais respostas de IA", + "askOwnerToUpgradeToPro": "Seu espaço de trabalho está ficando sem armazenamento gratuito. Peça ao proprietário do seu espaço de trabalho para atualizar para o Plano Pro", + "askOwnerToUpgradeToAIMax": "Seu espaço de trabalho está ficando sem respostas de IA gratuitas. Peça ao proprietário do seu espaço de trabalho para atualizar o plano ou adquirir complementos de IA", + "purchaseStorageSpace": "Adquirir espaço de armazenamento", + "purchaseAIResponse": "Adquirir ", + "askOwnerToUpgradeToLocalAI": "Peça ao proprietário do espaço de trabalho para habilitar a IA no dispositivo", + "upgradeToAILocal": "Execute modelos locais no seu dispositivo para máxima privacidade", + "upgradeToAILocalDesc": "Converse com PDFs, melhore sua escrita e preencha tabelas automaticamente usando IA local" }, "notifications": { "export": { @@ -206,6 +328,7 @@ }, "button": { "ok": "OK", + "confirm": "Confirmar", "done": "Feito", "cancel": "Cancelar", "signIn": "Conectar", @@ -228,11 +351,34 @@ "update": "Atualizar", "share": "Compartilhar", "removeFromFavorites": "Remover dos favoritos", + "removeFromRecent": "Remover dos recentes", "addToFavorites": "Adicionar aos favoritos", + "favoriteSuccessfully": "Adicionado aos favoritos", + "unfavoriteSuccessfully": "Removido dos favoritos", + "duplicateSuccessfully": "Duplicado com sucesso", "rename": "Renomear", "helpCenter": "Central de Ajuda", "add": "Adicionar", "yes": "Sim", + "no": "Não", + "clear": "Limpar", + "remove": "Remover", + "dontRemove": "Não remova", + "copyLink": "Copiar Link", + "align": "Alinhar", + "login": "Entrar", + "logout": "Sair", + "deleteAccount": "Deletar conta", + "back": "Voltar", + "signInGoogle": "Continuar com o Google", + "signInGithub": "Continuar com o Github", + "signInDiscord": "Continuar com o Discord", + "more": "Mais", + "create": "Criar", + "close": "Fechar", + "next": "Próximo", + "previous": "Anterior", + "submit": "Enviar", "tryAGain": "Tentar novamente" }, "label": { @@ -257,6 +403,190 @@ }, "settings": { "title": "Configurações", + "popupMenuItem": { + "settings": "Configurações", + "members": "Membros", + "trash": "Lixo", + "helpAndSupport": "Ajuda e Suporte" + }, + "accountPage": { + "menuLabel": "Minha conta", + "title": "Minha conta", + "general": { + "title": "Nome da conta e foto de perfil", + "changeProfilePicture": "Alterar foto do perfil" + }, + "email": { + "title": "E-mail", + "actions": { + "change": "Alterar e-mail" + } + }, + "login": { + "title": "Entrar com uma conta", + "loginLabel": "Entrar", + "logoutLabel": "Sair" + } + }, + "workspacePage": { + "menuLabel": "Espaço de trabalho", + "title": "Espaço de trabalho", + "description": "Personalize a aparência do seu espaço de trabalho, tema, fonte, layout de texto, formato de data/hora e idioma.", + "workspaceName": { + "title": "Nome do espaço de trabalho" + }, + "workspaceIcon": { + "title": "Ícone do espaço de trabalho", + "description": "Carregue uma imagem ou use um emoji para seu espaço de trabalho. O ícone será exibido na sua barra lateral e notificações." + }, + "appearance": { + "title": "Aparência", + "description": "Personalize a aparência do seu espaço de trabalho, tema, fonte, layout de texto, data, hora e idioma.", + "options": { + "system": "Automático", + "light": "Claro", + "dark": "Escuro" + } + }, + "resetCursorColor": { + "title": "Redefinir a cor do cursor do documento", + "description": "Tem certeza de que deseja redefinir a cor do cursor?" + }, + "resetSelectionColor": { + "title": "Redefinir cor de seleção de documento", + "description": "Tem certeza de que deseja redefinir a cor de seleção?" + }, + "theme": { + "title": "Tema", + "description": "Selecione um tema predefinido ou carregue seu próprio tema personalizado.", + "uploadCustomThemeTooltip": "Carregar um tema personalizado" + }, + "workspaceFont": { + "title": "Fonte do espaço de trabalho", + "noFontHint": "Nenhuma fonte encontrada, tente outro termo." + }, + "textDirection": { + "title": "Direção do texto", + "leftToRight": "Da esquerda para a direita", + "rightToLeft": "Da direita para a esquerda", + "auto": "Automático", + "enableRTLItems": "Habilitar items da barra de ferramenta da direita para a esquerda" + }, + "layoutDirection": { + "title": "Direção do layout", + "leftToRight": "Da esquerda para a direita", + "rightToLeft": "Da direita para a esquerda" + }, + "dateTime": { + "title": "Data e hora", + "example": "{} as {} ({})", + "24HourTime": "Tempo de 24 horas", + "dateFormat": { + "label": "Formato de data", + "us": "EUA", + "friendly": "Amigável" + } + }, + "language": { + "title": "Língua" + }, + "deleteWorkspacePrompt": { + "title": "Excluir espaço de trabalho", + "content": "Tem certeza de que deseja excluir este espaço de trabalho? Esta ação não pode ser desfeita, e quaisquer páginas que você tenha publicado deixarão de estar publicadas." + }, + "leaveWorkspacePrompt": { + "title": "Sair do espaço de trabalho", + "content": "Tem certeza de que deseja sair deste espaço de trabalho? Você perderá o acesso a todas as páginas e dados dentro dele." + }, + "manageWorkspace": { + "title": "Gerenciar espaço de trabalho", + "leaveWorkspace": "Sair do espaço de trabalho", + "deleteWorkspace": "Excluir espaço de trabalho" + } + }, + "manageDataPage": { + "menuLabel": "Gerenciar dados", + "title": "Gerenciar dados", + "description": "Gerencie o armazenamento local de dados ou importe seus dados existentes para @:appName .", + "dataStorage": { + "title": "Local de armazenamento de arquivos", + "tooltip": "O local onde seus arquivos são armazenados", + "actions": { + "change": "Mudar caminho", + "open": "Abrir pasta", + "openTooltip": "Abrir local da pasta de dados atual", + "copy": "Copiar caminho", + "copiedHint": "Caminho copiado!", + "resetTooltip": "Redefinir para o local padrão" + }, + "resetDialog": { + "title": "Tem certeza?", + "description": "Redefinir o caminho para o local de dados padrão não excluirá seus dados. Se você quiser reimportar seus dados atuais, você deve copiar o caminho do seu local atual primeiro." + } + }, + "importData": { + "title": "Importar dados", + "tooltip": "Importar dados das pastas de backups/dados de @:appName", + "description": "Copiar dados de uma pasta de dados externa ao @:appName", + "action": "Selecionar arquivo" + }, + "encryption": { + "title": "Criptografia", + "tooltip": "Gerencie como seus dados são armazenados e criptografados", + "descriptionNoEncryption": "Ativar a criptografia criptografará todos os dados. Isso não pode ser desfeito.", + "descriptionEncrypted": "Seus dados estão criptografados.", + "action": "Criptografar dados", + "dialog": { + "title": "Criptografar todos os seus dados?", + "description": "Criptografar todos os seus dados manterá seus dados seguros e protegidos. Esta ação NÃO pode ser desfeita. Tem certeza de que deseja continuar?" + } + }, + "cache": { + "title": "Limpar cache", + "description": "Ajude a resolver problemas como imagem não carregando, páginas faltando em um espaço e fontes não carregando. Isso não afetará seus dados.", + "dialog": { + "title": "Limpar cache", + "description": "Ajude a resolver problemas como imagem não carregando, páginas faltando em um espaço e fontes não carregando. Isso não afetará seus dados.", + "successHint": "Cache limpo!" + } + }, + "data": { + "fixYourData": "Corrija seus dados", + "fixButton": "Corrigir", + "fixYourDataDescription": "Se estiver com problemas com seus dados, você pode tentar corrigi-los aqui." + } + }, + "shortcutsPage": { + "menuLabel": "Atalhos", + "title": "Atalhos", + "editBindingHint": "Insira uma nova combinação", + "searchHint": "Pesquisar", + "actions": { + "resetDefault": "Redefinir padrão" + }, + "errorPage": { + "message": "Falha ao carregar atalhos: {}", + "howToFix": "Por favor, tente novamente. Se o problema persistir, entre em contato pelo GitHub." + }, + "resetDialog": { + "title": "Redefinir atalhos", + "description": "Isso redefinirá todas as suas combinações de teclas para o padrão. Você não poderá desfazer isso depois. Tem certeza de que deseja continuar?", + "buttonLabel": "Redefinir" + }, + "conflictDialog": { + "title": "{} já está em uso", + "descriptionPrefix": "Esta combinação de teclas está sendo usada atualmente por ", + "descriptionSuffix": ". Se você substituir esta combinação de teclas, ela será removida de {}.", + "confirmLabel": "Continuar" + }, + "editTooltip": "Pressione para começar a editar a combinação de teclas", + "keybindings": { + "toggleToDoList": "Alternar para a lista de tarefas", + "insertNewParagraphInCodeblock": "Inserir novo parágrafo", + "pasteInCodeblock": "Colar bloco de código", + "selectAllCodeblock": "Selecionar tudo" + } + }, "menu": { "appearance": "Aparência", "language": "Idioma", From f20f8bcfbf536577d41e5e3ae8c27415f5e3edbb Mon Sep 17 00:00:00 2001 From: vavenCV <82443097+vavenCV@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:17:42 +0200 Subject: [PATCH 13/60] chore: update Fr-fr translations (#6106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update translations with Fink 🐦 * chore: update translations with Fink 🐦 --------- Co-authored-by: Lucas.Xu --- frontend/resources/translations/fr-FR.json | 36 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index 8494f63b8f28f..8f244c0e07187 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -36,6 +36,7 @@ "loginButtonText": "Connexion", "loginStartWithAnonymous": "Lancer avec une session anonyme", "continueAnonymousUser": "Continuer avec une session anonyme", + "anonymous": "Anonyme", "buttonText": "Se connecter", "signingInText": "Connexion en cours...", "forgotPassword": "Mot de passe oublié ?", @@ -50,6 +51,8 @@ "signInWithGoogle": "Continuer avec Google", "signInWithGithub": "Continuer avec Github", "signInWithDiscord": "Continuer avec Discord", + "signInWithApple": "Continuer avec Apple", + "continueAnotherWay": "Continuer autrement", "signUpWithGoogle": "S'inscrire avec Google", "signUpWithGithub": "S'inscrire avec Github", "signUpWithDiscord": "S'inscrire avec Discord", @@ -166,6 +169,7 @@ "inputMessageHint": "Demandez à l'IA @:appName", "inputLocalAIMessageHint": "Demander l'IA locale @:appName", "unsupportedCloudPrompt": "Cette fonctionnalité n'est disponible que lors de l'utilisation du cloud @:appName", + "relatedQuestion": "Questions Associées", "serverUnavailable": "Service temporairement indisponible. Veuillez réessayer ultérieurement.", "aiServerUnavailable": "🌈 Oh-oh ! 🌈. Une licorne a mangé notre réponse. Veuillez réessayer !", "clickToRetry": "Cliquez pour réessayer", @@ -379,6 +383,7 @@ "next": "Suivant", "previous": "Précédent", "submit": "Soumettre", + "download": "Télécharger", "tryAGain": "Réessayer" }, "label": { @@ -403,6 +408,12 @@ }, "settings": { "title": "Paramètres", + "popupMenuItem": { + "settings": "Paramètres", + "members": "Membres", + "trash": "Poubelle", + "helpAndSupport": "Aide et Support" + }, "accountPage": { "menuLabel": "Mon compte", "title": "Mon compte", @@ -667,6 +678,7 @@ "keys": { "enableAISearchTitle": "Recherche IA", "aiSettingsDescription": "Choisissez votre modèle préféré pour alimenter AppFlowy AI. Inclut désormais GPT 4-o, Claude 3,5, Llama 3.1 et Mistral 7B", + "loginToEnableAIFeature": "Les fonctionnalités d'IA sont accessibles uniquement après s'être connecté avec @:appName Cloud. Pour créer un compte @:appName, voir dans la rubrique 'Mon Compte'.", "llmModel": "Modèle de langage", "llmModelType": "Type de modèle de langue", "downloadLLMPrompt": "Télécharger {}", @@ -699,9 +711,15 @@ "planUsage": { "title": "Résumé de l'utilisation du plan", "storageLabel": "Stockage", + "storageUsage": "{} de {} GB", "unlimitedStorageLabel": "Stockage illimité", "collaboratorsLabel": "Membres", + "collaboratorsUsage": "{} de {}", "aiResponseLabel": "Réponses de l'IA", + "aiResponseUsage": "{} de {}", + "unlimitedAILabel": "Réponses non limitées", + "proBadge": "Pro", + "aiMaxBadge": "AI Max", "memberProToggle": "Plus de membres et une IA illimitée", "aiMaxToggle": "IA illimitée et accès à des modèles avancés", "aiOnDeviceToggle": "IA locale pour une confidentialité ultime", @@ -772,6 +790,10 @@ "description": "Débloquez une IA illimitée locale sur votre appareil", "activeDescription": "Prochaine facture due le {}", "canceledDescription": "IA locale pour Mac sera disponible jusqu'au {}" + }, + "removeDialog": { + "title": "Supprimer", + "description": "Êtes-vous sûr de vouloir supprimer {plan}? Vous perdrez l'accès aux fonctionnalités et bénéfices de {plan} de manière immédiate." } }, "currentPeriodBadge": "ACTUEL", @@ -786,6 +808,8 @@ "planFeatures": "Plan\nCaractéristiques", "current": "Actuel", "actions": { + "upgrade": "Améliorer", + "downgrade": "Rétrograder", "current": "Actuel" }, "freePlan": { @@ -833,6 +857,7 @@ } }, "cancelSurveyDialog": { + "title": "Désolé de vous voir partir", "commonOther": "Autre", "otherHint": "Écrivez votre réponse ici", "questionOne": { @@ -944,7 +969,8 @@ }, "action": { "markAsRead": "Marquer comme lu", - "multipleChoice": "Sélectionnez plus" + "multipleChoice": "Sélectionnez plus", + "archive": "Archiver" }, "settings": { "settings": "Paramètres", @@ -970,6 +996,7 @@ }, "refreshSuccess": "Les notifications ont été actualisées avec succès", "titles": { + "notifications": "Notifications", "reminder": "Rappel" } }, @@ -1078,7 +1105,9 @@ "removeMember": "Supprimer un membre", "areYouSureToRemoveMember": "Êtes-vous sûr de vouloir supprimer ce membre ?", "inviteMemberSuccess": "L'invitation a été envoyée avec succès", - "failedToInviteMember": "Impossible d'inviter un membre" + "failedToInviteMember": "Impossible d'inviter un membre", + "workspaceMembersError": "Une erreur s'est produite", + "workspaceMembersErrorDescription": "Nous n'avons pas pu charger la liste des membres. Veuillez essayer plus tard s'il vous plait" } }, "files": { @@ -1281,6 +1310,8 @@ "urlFieldName": "URL", "checklistFieldName": "Check-list", "relationFieldName": "Relation", + "summaryFieldName": "Résumé IA", + "translateFieldName": "Traduction IA", "translateTo": "Traduire en", "numberFormat": "Format du nombre", "dateFormat": "Format de la date", @@ -1450,6 +1481,7 @@ "heading1": "Titre 1", "heading2": "Titre 2", "heading3": "Titre 3", + "image": "Image", "bulletedList": "Liste à puces", "numberedList": "Liste numérotée", "linkedDoc": "Lien vers la page", From 3324e7837b04f88de5125a48ed0388797bb807c3 Mon Sep 17 00:00:00 2001 From: Aymane Boumaaza Date: Fri, 30 Aug 2024 14:22:01 +0100 Subject: [PATCH 14/60] =?UTF-8?q?chore:=20update=20ar-SA=20and=20fr-FR=20t?= =?UTF-8?q?ranslations=20with=20Fink=20=F0=9F=90=A6=20(#6084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lucas.Xu --- frontend/resources/translations/ar-SA.json | 25 +++++++++++++- frontend/resources/translations/fr-FR.json | 38 ++++++++++++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index b50c2e98058bf..8b58af8d7b696 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -35,6 +35,7 @@ "loginButtonText": "تسجيل الدخول", "loginStartWithAnonymous": "ابدأ بجلسة خفية", "continueAnonymousUser": "استمر بجلسة خفية", + "anonymous": "مجهول", "buttonText": "تسجيل الدخول", "signingInText": "جاري تسجيل الدخول...", "forgotPassword": "هل نسيت كلمة السر؟", @@ -248,11 +249,16 @@ "closeSidebar": "إغلاق الشريط الجانبي", "openSidebar": "فتح الشريط الجانبي", "personal": "شخصي", + "private": "خاص", "favorites": "المفضلة", "clickToHidePersonal": "انقر لإخفاء القسم الشخصي", "clickToHideFavorites": "انقر لإخفاء القسم المفضل", "addAPage": "أضف صفحة", - "recent": "مؤخرًا" + "recent": "مؤخرًا", + "today": "اليوم", + "thisWeek": "هذا الأسبوع", + "justNow": "الآن", + "purchaseAIResponse": "شراء" }, "notifications": { "export": { @@ -268,6 +274,7 @@ }, "button": { "ok": "حسنا", + "confirm": "تأكيد", "done": "منتهي", "cancel": "الغاء", "signIn": "تسجيل الدخول", @@ -295,7 +302,19 @@ "helpCenter": "مركز المساعدة", "add": "اضافة", "yes": "نعم", + "no": "لا", + "remove": "حذف", + "copyLink": "نسخ الرابط", + "login": "تسجيل الدخول", + "logout": "تسجيل الخروج", + "deleteAccount": "حذف الحساب", "back": "خلف", + "more": "أكثر", + "create": "إنشاء", + "close": "إغلاق", + "next": "التالي", + "previous": "السابق", + "download": "تحميل", "tryAGain": "حاول ثانية" }, "label": { @@ -320,6 +339,10 @@ }, "settings": { "title": "إعدادات", + "popupMenuItem": { + "settings": "إعدادات", + "members": "الأعضاء" + }, "menu": { "appearance": "مظهر", "language": "لغة", diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index 8f244c0e07187..e83285b1d199b 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -51,8 +51,8 @@ "signInWithGoogle": "Continuer avec Google", "signInWithGithub": "Continuer avec Github", "signInWithDiscord": "Continuer avec Discord", - "signInWithApple": "Continuer avec Apple", - "continueAnotherWay": "Continuer autrement", + "signInWithApple": "Se connecter via Apple", + "continueAnotherWay": "Continuer via une autre méthode", "signUpWithGoogle": "S'inscrire avec Google", "signUpWithGithub": "S'inscrire avec Github", "signUpWithDiscord": "S'inscrire avec Discord", @@ -411,8 +411,8 @@ "popupMenuItem": { "settings": "Paramètres", "members": "Membres", - "trash": "Poubelle", - "helpAndSupport": "Aide et Support" + "trash": "Corbeille", + "helpAndSupport": "Aide & Support" }, "accountPage": { "menuLabel": "Mon compte", @@ -711,13 +711,13 @@ "planUsage": { "title": "Résumé de l'utilisation du plan", "storageLabel": "Stockage", - "storageUsage": "{} de {} GB", + "storageUsage": "{} sur {} GB", "unlimitedStorageLabel": "Stockage illimité", "collaboratorsLabel": "Membres", - "collaboratorsUsage": "{} de {}", + "collaboratorsUsage": "{} sur {}", "aiResponseLabel": "Réponses de l'IA", - "aiResponseUsage": "{} de {}", - "unlimitedAILabel": "Réponses non limitées", + "aiResponseUsage": "{} sur {}", + "unlimitedAILabel": "Réponses illimitées", "proBadge": "Pro", "aiMaxBadge": "AI Max", "memberProToggle": "Plus de membres et une IA illimitée", @@ -725,6 +725,7 @@ "aiOnDeviceToggle": "IA locale pour une confidentialité ultime", "aiCredit": { "title": "Ajoutez des crédit IA @:appName ", + "price": "{}", "priceDescription": "pour 1 000 crédits", "purchase": "Acheter l'IA", "infoItemOne": "10 000 réponses par base de données", @@ -733,6 +734,7 @@ "currentPlan": { "bannerLabel": "Plan actuel", "freeTitle": "Gratuit", + "proTitle": "Pro", "teamTitle": "Équipe", "freeInfo": "Idéal pour les particuliers jusqu'à 2 membres pour tout organiser", "proInfo": "Idéal pour les petites et moyennes équipes jusqu'à 10 membres.", @@ -745,10 +747,13 @@ "addLabel": "Ajouter", "activeLabel": "Ajouté", "aiMax": { + "title": "AI Max", + "price": "{}", "priceInfo": "par utilisateur et par mois, facturé annuellement" }, "aiOnDevice": { "description": "Exécutez Mistral 7B, LLAMA 3 et d'autres modèles locaux sur votre machine", + "price": "{}", "priceInfo": "par utilisateur et par mois, facturé annuellement", "recommend": "Recommand2 M1 ou plus récent" } @@ -764,7 +769,9 @@ "menuLabel": "Facturation", "title": "Facturation", "plan": { + "title": "Plan", "freeLabel": "Gratuit", + "proLabel": "Pro", "planButtonLabel": "Changer de plan", "billingPeriod": "Période de facturation", "periodButtonLabel": "Changer la période " @@ -815,10 +822,13 @@ "freePlan": { "title": "Gratuit", "description": "Pour les particuliers jusqu'à 2 membres pour tout organiser", + "price": "{}", "priceInfo": "gratuit pour toujours" }, "proPlan": { + "title": "Pro", "description": "Pour les petites équipes pour gérer les projets et les bases de connaissance", + "price": "{}", "priceInfo": "par utilisateur et par mois\nfacturé annuellement\n\n{} facturé mensuellement" }, "planLabels": { @@ -1100,6 +1110,7 @@ "memberLimitExceeded": "Vous avez atteint la limite maximale de membres autorisée pour votre compte. Si vous souhaitez ajouter d'autres membres pour continuer votre travail, veuillez en faire la demande sur Github.", "memberLimitExceededUpgrade": "mise à niveau", "memberLimitExceededPro": "Limite de membres atteinte, si vous avez besoin de plus de membres, contactez ", + "memberLimitExceededProContact": "support@appflowy.io", "failedToAddMember": "Échec de l'ajout d'un membre", "addMemberSuccess": "Membre ajouté avec succès", "removeMember": "Supprimer un membre", @@ -1310,7 +1321,7 @@ "urlFieldName": "URL", "checklistFieldName": "Check-list", "relationFieldName": "Relation", - "summaryFieldName": "Résumé IA", + "summaryFieldName": "Résume IA", "translateFieldName": "Traduction IA", "translateTo": "Traduire en", "numberFormat": "Format du nombre", @@ -1487,6 +1498,7 @@ "linkedDoc": "Lien vers la page", "grid": "Grille", "linkedGrid": "Grille liée", + "kanban": "Kanban", "linkedKanban": "Kanban lié", "calendar": "Calendrier", "linkedCalendar": "Calendrier lié", @@ -1494,6 +1506,7 @@ "divider": "Diviseur", "table": "Tableau", "mathEquation": "Équation mathématique", + "code": "Code", "emoji": "Émoji", "aiWriter": "Rédacteur IA", "dateOrReminder": "Date ou rappel", @@ -1599,7 +1612,9 @@ }, "photoGallery": { "name": "Galerie de photos", + "imageKeyword": "image", "imageGalleryKeyword": "Galerie d'images", + "photoKeyword": "photo", "photoBrowserKeyword": "navigateur de photos", "galleryKeyword": "galerie", "addImageTooltip": "Ajouter une image", @@ -1732,6 +1747,7 @@ "openLocalImage": "Ouvrir l'image", "downloadImage": "Télécharger l'image", "closeViewer": "Fermer la visionneuse", + "scalePercentage": "{}%", "deleteImageTooltip": "Supprimer l'image" } }, @@ -1740,7 +1756,8 @@ "codeBlock": { "language": { "label": "Langue", - "placeholder": "Choisir la langue" + "placeholder": "Choisir la langue", + "auto": "Auto" }, "copyTooltip": "Copier le contenu du bloc de code", "searchLanguageHint": "Rechercher une langue", @@ -1825,6 +1842,7 @@ "failedToLoad": "Échec du chargement de la vue du tableau" }, "dateCondition": { + "weekOf": "Semaine de {} - {}", "today": "Aujourd'hui", "yesterday": "Hier", "tomorrow": "Demain", From 8139065113b610ed9185fc4401a5e3216aef4190 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:12:20 +0800 Subject: [PATCH 15/60] fix: init database row init (#6127) --- .../database/card/card_detail/mobile_card_detail_screen.dart | 2 ++ .../database/application/row/related_row_detail_bloc.dart | 3 +++ .../lib/plugins/database/application/row/row_controller.dart | 3 +++ .../board/presentation/widgets/board_hidden_groups.dart | 1 + .../calendar/application/calendar_event_editor_bloc.dart | 2 ++ .../database/grid/application/row/row_detail_bloc.dart | 2 ++ .../lib/plugins/database/widgets/card/card_bloc.dart | 1 + .../presentation/database_document_title_bloc.dart | 4 ++++ 8 files changed, 18 insertions(+) diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index 6a54646301170..1708583453a59 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -306,6 +306,8 @@ class MobileRowDetailPageContentState viewId: viewId, rowCache: rowCache, ); + rowController.initialize(); + cellBuilder = EditableCellBuilder( databaseController: widget.databaseController, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart index 1390d9ff97bac..b03a5c69fef9a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart @@ -35,6 +35,8 @@ class RelatedRowDetailPageBloc on((event, emit) async { event.when( didInitialize: (databaseController, rowController) { + rowController.initialize(); + state.maybeWhen( ready: (_, oldRowController) async { await oldRowController.dispose(); @@ -93,6 +95,7 @@ class RelatedRowDetailPageBloc viewId: inlineView.id, rowCache: databaseController.rowCache, ); + add( RelatedRowDetailPageEvent.didInitialize( databaseController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart index 1ba336b321dde..4dd42d9200c13 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart @@ -38,6 +38,9 @@ class RowController { List loadCells() => _rowCache.loadCells(rowMeta); + /// This method must be called to initialize the row controller; otherwise, the row will not sync between devices. + /// When creating a row controller, calling [initialize] immediately may not be necessary. + /// Only call [initialize] when the row becomes visible. This approach helps reduce unnecessary sync operations. Future initialize() async { await _rowBackendSvc.initRow(rowMeta.id); unawaited( diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart index a44cf1c55d3e0..48cdcf8ef641e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart @@ -397,6 +397,7 @@ class HiddenGroupPopupItemList extends StatelessWidget { viewId: viewId, rowCache: rowCache, ); + rowController.initialize(); final databaseController = context.read().databaseController; diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart index b122d951bedca..48e159475c4c8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart @@ -29,6 +29,8 @@ class CalendarEventEditorBloc (event, emit) async { await event.when( initial: () { + rowController.initialize(); + _startListening(); final primaryFieldId = fieldController.fieldInfos .firstWhere((fieldInfo) => fieldInfo.isPrimary) diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart index 5c25fc851f153..bf5aff3a4a8b5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart @@ -20,6 +20,8 @@ class RowDetailBloc extends Bloc { _dispatch(); _startListening(); _init(); + + rowController.initialize(); } final FieldController fieldController; diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart index fa066db319ec8..a8ede243f73ed 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart @@ -32,6 +32,7 @@ class CardBloc extends Bloc { rowController.rowMeta, ), ) { + rowController.initialize(); _dispatch(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title_bloc.dart index 5f8bf7ca08aec..f35f0ee8f6036 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; @@ -87,6 +89,8 @@ class DatabaseDocumentTitleBloc viewId: view.id, rowCache: databaseController.rowCache, ); + unawaited(rowController.initialize()); + final primaryFieldId = await FieldBackendService.getPrimaryField(viewId: view.id).fold( (primaryField) => primaryField.id, From d264f3dbde725806a8a3c04f027615d9ea0a9b00 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 31 Aug 2024 09:33:44 +0800 Subject: [PATCH 16/60] fix: android toast style (#6128) --- .../presentation/home/mobile_home_page.dart | 6 +-- .../setting/workspace/member_list.dart | 37 ++++++++++++++++--- .../popup_menu/appflowy_popup_menu.dart | 2 +- .../menu/sidebar/space/shared_widget.dart | 5 ++- .../lib/style_widget/button.dart | 35 +++++++++++++----- 5 files changed, 64 insertions(+), 21 deletions(-) diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart index 1078d12b1fe93..f2d866533d0ee 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart'; import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart'; @@ -197,10 +195,10 @@ class _HomePageState extends State<_HomePage> { children: [ // Header Padding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: HomeSpaceViewSizes.mHorizontalPadding, right: 8.0, - top: Platform.isAndroid ? 8.0 : 0.0, + ), child: MobileHomePageHeader( userProfile: widget.userProfile, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart index 1d9f250d3a3fa..7c653209ab3f5 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart @@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/shared/af_role_pb_extension.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' show PlatformExtension; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -72,10 +73,10 @@ class _MemberItem extends StatelessWidget { final canDelete = myRole.canDelete && member.email != userProfile.email; final textColor = member.role.isOwner ? Theme.of(context).hintColor : null; - Widget child = Container( - height: 48, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( + Widget child; + + if (PlatformExtension.isDesktop) { + child = Row( children: [ Expanded( child: FlowyText.medium( @@ -93,7 +94,33 @@ class _MemberItem extends StatelessWidget { ), ), ], - ), + ); + } else { + child = Row( + children: [ + Expanded( + child: FlowyText.medium( + member.name, + color: textColor, + fontSize: 15.0, + overflow: TextOverflow.ellipsis, + ), + ), + const HSpace(36.0), + FlowyText.medium( + member.role.description, + color: textColor, + fontSize: 15.0, + textAlign: TextAlign.end, + ), + ], + ); + } + + child = Container( + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: child, ); if (canDelete) { diff --git a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart index 0c3d759744f0f..0de43a84e848a 100644 --- a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart @@ -1506,7 +1506,7 @@ class PopupMenuButtonState extends State> { if (items.isNotEmpty) { var popUpAnimationStyle = widget.popUpAnimationStyle; if (popUpAnimationStyle == null && - Theme.of(context).platform == TargetPlatform.iOS) { + defaultTargetPlatform == TargetPlatform.iOS) { popUpAnimationStyle = AnimationStyle( curve: Curves.easeInOut, duration: const Duration(milliseconds: 300), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index bfe836ba46ba3..0f3bebc7e8376 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -321,11 +321,14 @@ class _ConfirmPopupState extends State { Navigator.of(context).pop(); } }, - child: Padding( + child: Container( padding: const EdgeInsets.symmetric( vertical: 20.0, horizontal: 20.0, ), + color: PlatformExtension.isDesktop + ? null + : Theme.of(context).colorScheme.surface, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 742fb9baffdad..1db56b826792a 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -258,17 +258,32 @@ class FlowyButton extends StatelessWidget { child = IntrinsicWidth(child: child); } - final decoration = this.decoration ?? + var decoration = this.decoration; + + if (decoration == null && (showDefaultBoxDecorationOnMobile && - (Platform.isIOS || Platform.isAndroid) - ? BoxDecoration( - border: Border.all( - color: borderColor ?? Theme.of(context).colorScheme.outline, - width: 1.0, - ), - borderRadius: radius, - ) - : null); + (Platform.isIOS || Platform.isAndroid))) { + decoration = BoxDecoration( + color: backgroundColor ?? Theme.of(context).colorScheme.surface, + ); + } + + if (decoration == null && (Platform.isIOS || Platform.isAndroid)) { + if (showDefaultBoxDecorationOnMobile) { + decoration = BoxDecoration( + border: Border.all( + color: borderColor ?? Theme.of(context).colorScheme.outline, + width: 1.0, + ), + borderRadius: radius, + ); + } else if (backgroundColor != null) { + decoration = BoxDecoration( + color: backgroundColor, + borderRadius: radius, + ); + } + } return Container( decoration: decoration, From 01d7d6e9007707e21310cdbd087bd779cb79732e Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sat, 31 Aug 2024 23:54:37 +0800 Subject: [PATCH 17/60] chore: show document icon or emoji (#6144) * chore: show icon * chore: rename * chore: adjust UI * chore: remove unused property * chore: update test * chore: fix test * chore: fix test --- .github/workflows/flutter_ci.yaml | 2 +- .github/workflows/rust_ci.yaml | 2 + .../application/cell/bloc/text_cell_bloc.dart | 27 ++++---- .../application/cell/cell_controller.dart | 34 +--------- .../database/application/row/row_cache.dart | 58 ++++++++++++---- .../database/application/row/row_list.dart | 23 +++++-- .../database/application/row/row_service.dart | 8 +-- .../calendar/application/calendar_bloc.dart | 2 +- .../application/unschedule_event_bloc.dart | 2 +- .../card_cell_skeleton/text_card_cell.dart | 5 +- .../desktop_grid/desktop_grid_text_cell.dart | 68 +++++++++++++------ .../mobile_grid/mobile_grid_text_cell.dart | 2 +- .../application/database/row/row_service.ts | 10 +-- .../src/database_event.rs | 6 +- .../tests/database/local_test/test.rs | 8 +-- .../tests/document/af_cloud_test/edit_test.rs | 4 +- .../user/af_cloud_test/anon_user_test.rs | 8 +-- .../import_af_data_folder_test.rs | 50 +++++++------- .../user/af_cloud_test/workspace_test.rs | 34 ++++++---- .../src/entities/row_entities.rs | 10 +-- .../flowy-database2/src/event_handler.rs | 10 +-- .../rust-lib/flowy-database2/src/event_map.rs | 14 ++-- .../src/services/database/database_editor.rs | 2 +- 23 files changed, 216 insertions(+), 173 deletions(-) diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 840ac69064bb1..580e6f176dca6 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -246,7 +246,7 @@ jobs: - name: Run Docker-Compose working-directory: AppFlowy-Cloud env: - BACKEND_VERSION: 0.3.24-amd64 + BACKEND_VERSION: 0.6.4-amd64 run: | if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then docker compose pull diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 42a8794edab31..4f58c5fb2db2a 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -106,6 +106,8 @@ jobs: - name: Run Docker-Compose working-directory: AppFlowy-Cloud + env: + BACKEND_VERSION: 0.6.4-amd64 run: | if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then docker compose pull diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart index 22baf2659996a..dab09c02f78a2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -42,8 +43,8 @@ class TextCellBloc extends Bloc { emit(state.copyWith(wrap: wrap)); } }, - didUpdateEmoji: (String emoji) { - emit(state.copyWith(emoji: emoji)); + didUpdateEmoji: (String emoji, bool hasDocument) { + // emit(state.copyWith(emoji: emoji, hasDocument: hasDocument)); }, updateText: (String text) { if (state.content != text) { @@ -66,13 +67,6 @@ class TextCellBloc extends Bloc { } }, onFieldChanged: _onFieldChangedListener, - onRowMetaChanged: cellController.fieldInfo.isPrimary - ? () { - if (!isClosed) { - add(TextCellEvent.didUpdateEmoji(cellController.icon ?? "")); - } - } - : null, ); } @@ -91,14 +85,18 @@ class TextCellEvent with _$TextCellEvent { _DidUpdateField; const factory TextCellEvent.updateText(String text) = _UpdateText; const factory TextCellEvent.enableEdit(bool enabled) = _EnableEdit; - const factory TextCellEvent.didUpdateEmoji(String emoji) = _UpdateEmoji; + const factory TextCellEvent.didUpdateEmoji( + String emoji, + bool hasDocument, + ) = _UpdateEmoji; } @freezed class TextCellState with _$TextCellState { const factory TextCellState({ required String content, - required String emoji, + required ValueNotifier? emoji, + required ValueNotifier? hasDocument, required bool enableEdit, required bool wrap, }) = _TextCellState; @@ -106,13 +104,16 @@ class TextCellState with _$TextCellState { factory TextCellState.initial(TextCellController cellController) { final cellData = cellController.getCellData() ?? ""; final wrap = cellController.fieldInfo.wrapCellContent ?? true; - final emoji = - cellController.fieldInfo.isPrimary ? cellController.icon ?? "" : ""; + ValueNotifier? emoji; + if (cellController.fieldInfo.isPrimary) { + emoji = cellController.icon; + } return TextCellState( content: cellData, emoji: emoji, enableEdit: false, + hasDocument: cellController.hasDocument, wrap: wrap, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart index 51c372fb6eec3..2fd71d744feb2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart @@ -5,7 +5,6 @@ import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/domain/cell_listener.dart'; import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart'; -import 'package:appflowy/plugins/database/domain/row_meta_listener.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; @@ -61,10 +60,8 @@ class CellController { final CellDataPersistence _cellDataPersistence; CellListener? _cellListener; - RowMetaListener? _rowMetaListener; CellDataNotifier? _cellDataNotifier; - VoidCallback? _onRowMetaChanged; Timer? _loadDataOperation; Timer? _saveDataOperation; @@ -75,8 +72,9 @@ class CellController { FieldInfo get fieldInfo => _fieldController.getField(_cellContext.fieldId)!; FieldType get fieldType => _fieldController.getField(_cellContext.fieldId)!.fieldType; - RowMetaPB? get rowMeta => _rowCache.getRow(rowId)?.rowMeta; - String? get icon => rowMeta?.icon; + ValueNotifier? get icon => _rowCache.getRow(rowId)?.rowIconNotifier; + ValueNotifier? get hasDocument => + _rowCache.getRow(rowId)?.rowDocumentNotifier; CellMemCache get _cellCache => _rowCache.cellCache; /// casting method for painless type coersion @@ -89,15 +87,6 @@ class CellController { fieldId: _cellContext.fieldId, ); - _rowCache.addListener( - rowId: rowId, - onRowChanged: (context, reason) { - if (reason == const ChangedReason.didFetchRow()) { - _onRowMetaChanged?.call(); - } - }, - ); - // 1. Listen on user edit event and load the new cell data if needed. // For example: // user input: 12 @@ -116,23 +105,12 @@ class CellController { fieldId, onFieldChanged: _onFieldChangedListener, ); - - // 3. If the field is primary listen to row meta changes. - if (fieldInfo.field.isPrimary) { - _rowMetaListener = RowMetaListener(_cellContext.rowId); - _rowMetaListener?.start( - callback: (newRowMeta) { - _onRowMetaChanged?.call(); - }, - ); - } } /// Add a new listener VoidCallback? addListener({ required void Function(T?) onCellChanged, void Function(FieldInfo fieldInfo)? onFieldChanged, - VoidCallback? onRowMetaChanged, }) { /// an adaptor for the onCellChanged listener void onCellChangedFn() => onCellChanged(_cellDataNotifier?.value); @@ -145,8 +123,6 @@ class CellController { ); } - _onRowMetaChanged = onRowMetaChanged; - // Return the function pointer that can be used when calling removeListener. return onCellChangedFn; } @@ -242,9 +218,6 @@ class CellController { } Future dispose() async { - await _rowMetaListener?.stop(); - _rowMetaListener = null; - await _cellListener?.stop(); _cellListener = null; @@ -258,7 +231,6 @@ class CellController { _saveDataOperation?.cancel(); _cellDataNotifier?.dispose(); _cellDataNotifier = null; - _onRowMetaChanged = null; } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart index 4d9f594471454..2c671ebcc7031 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart @@ -92,12 +92,19 @@ class RowCache { } void setRowMeta(RowMetaPB rowMeta) { - final rowInfo = buildGridRow(rowMeta); - _rowList.add(rowInfo); + var rowInfo = _rowList.get(rowMeta.id); + if (rowInfo != null) { + rowInfo.updateRowMeta(rowMeta); + } else { + rowInfo = buildGridRow(rowMeta); + _rowList.add(rowInfo); + } + _changedNotifier?.receive(const ChangedReason.didFetchRow()); } void dispose() { + _rowList.dispose(); _rowLifeCycle.onRowDisposed(); _changedNotifier?.dispose(); _changedNotifier = null; @@ -176,8 +183,10 @@ class RowCache { } } - final updatedIndexs = - _rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId)); + final updatedIndexs = _rowList.updateRows( + rowMetas: updatedList, + builder: (rowId) => buildGridRow(rowId), + ); if (updatedIndexs.isNotEmpty) { _changedNotifier?.receive(ChangedReason.update(updatedIndexs)); @@ -251,9 +260,7 @@ class RowCache { final rowInfo = _rowList.get(rowMetaPB.id); final rowIndex = _rowList.indexOfRow(rowMetaPB.id); if (rowInfo != null && rowIndex != null) { - final updatedRowInfo = rowInfo.copyWith(rowMeta: rowMetaPB); - _rowList.remove(rowMetaPB.id); - _rowList.insert(rowIndex, updatedRowInfo); + rowInfo.rowMetaNotifier.value = rowMetaPB; final UpdatedIndexMap updatedIndexs = UpdatedIndexMap(); updatedIndexs[rowMetaPB.id] = UpdatedIndex( @@ -308,15 +315,38 @@ class RowChangesetNotifier extends ChangeNotifier { } } -@unfreezed -class RowInfo with _$RowInfo { - const RowInfo._(); - factory RowInfo({ - required UnmodifiableListView fields, +class RowInfo { + RowInfo({ + required this.fields, required RowMetaPB rowMeta, - }) = _RowInfo; + }) : rowMetaNotifier = ValueNotifier(rowMeta), + rowIconNotifier = ValueNotifier(rowMeta.icon), + rowDocumentNotifier = ValueNotifier( + !(rowMeta.hasIsDocumentEmpty() ? rowMeta.isDocumentEmpty : true), + ); - String get rowId => rowMeta.id; + final UnmodifiableListView fields; + final ValueNotifier rowMetaNotifier; + final ValueNotifier rowIconNotifier; + final ValueNotifier rowDocumentNotifier; + + String get rowId => rowMetaNotifier.value.id; + + RowMetaPB get rowMeta => rowMetaNotifier.value; + + /// Updates the RowMeta and automatically updates the related notifiers. + void updateRowMeta(RowMetaPB newMeta) { + rowMetaNotifier.value = newMeta; + rowIconNotifier.value = newMeta.icon; + rowDocumentNotifier.value = !newMeta.isDocumentEmpty; + } + + /// Dispose of the notifiers when they are no longer needed. + void dispose() { + rowMetaNotifier.dispose(); + rowIconNotifier.dispose(); + rowDocumentNotifier.dispose(); + } } typedef InsertedIndexs = List; diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_list.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_list.dart index b31a3022e9c31..00b074544870b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_list.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:math'; import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart'; @@ -117,20 +118,23 @@ class RowList { return deletedIndex; } - UpdatedIndexMap updateRows( - List rowMetas, - RowInfo Function(RowMetaPB) builder, - ) { + UpdatedIndexMap updateRows({ + required List rowMetas, + required RowInfo Function(RowMetaPB) builder, + }) { final UpdatedIndexMap updatedIndexs = UpdatedIndexMap(); for (final rowMeta in rowMetas) { final index = _rowInfos.indexWhere( (rowInfo) => rowInfo.rowId == rowMeta.id, ); if (index != -1) { + rowInfoByRowId[rowMeta.id]?.updateRowMeta(rowMeta); + } else { + final insertIndex = max(index, _rowInfos.length); final rowInfo = builder(rowMeta); - insert(index, rowInfo); + insert(insertIndex, rowInfo); updatedIndexs[rowMeta.id] = UpdatedIndex( - index: index, + index: insertIndex, rowId: rowMeta.id, ); } @@ -162,4 +166,11 @@ class RowList { bool contains(RowId rowId) { return rowInfoByRowId[rowId] != null; } + + void dispose() { + for (final rowInfo in _rowInfos) { + rowInfo.dispose(); + } + _rowInfos.clear(); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart index c5e71ba78b0a8..2db78e7e19feb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart @@ -38,7 +38,7 @@ class RowBackendService { } Future> initRow(RowId rowId) async { - final payload = RowIdPB() + final payload = DatabaseViewRowIdPB() ..viewId = viewId ..rowId = rowId; @@ -65,7 +65,7 @@ class RowBackendService { required String viewId, required String rowId, }) { - final payload = RowIdPB() + final payload = DatabaseViewRowIdPB() ..viewId = viewId ..rowId = rowId; @@ -73,7 +73,7 @@ class RowBackendService { } Future> getRowMeta(RowId rowId) { - final payload = RowIdPB.create() + final payload = DatabaseViewRowIdPB.create() ..viewId = viewId ..rowId = rowId; @@ -119,7 +119,7 @@ class RowBackendService { String viewId, RowId rowId, ) { - final payload = RowIdPB( + final payload = DatabaseViewRowIdPB( viewId: viewId, rowId: rowId, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_bloc.dart index 8c9e36c2c36a4..3e9b4ac7d7785 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_bloc.dart @@ -220,7 +220,7 @@ class CalendarBloc extends Bloc { } Future?> _loadEvent(RowId rowId) async { - final payload = RowIdPB(viewId: viewId, rowId: rowId); + final payload = DatabaseViewRowIdPB(viewId: viewId, rowId: rowId); return DatabaseEventGetCalendarEvent(payload).send().then((result) { return result.fold( (eventPB) => _calendarEventDataFromEventPB(eventPB), diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/unschedule_event_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/unschedule_event_bloc.dart index 208906f00b348..e0bcc071ed9ff 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/unschedule_event_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/unschedule_event_bloc.dart @@ -77,7 +77,7 @@ class UnscheduleEventsBloc Future _loadEvent( RowId rowId, ) async { - final payload = RowIdPB(viewId: viewId, rowId: rowId); + final payload = DatabaseViewRowIdPB(viewId: viewId, rowId: rowId); return DatabaseEventGetCalendarEvent(payload).send().then( (result) => result.fold( (eventPB) => eventPB, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart index 36d4f24142005..f5c1b3918b2f4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart @@ -140,12 +140,13 @@ class _TextCellState extends State { } Widget? _buildIcon(TextCellState state) { - if (state.emoji.isNotEmpty) { + if (state.emoji?.value.isNotEmpty ?? false) { return Text( - state.emoji, + state.emoji?.value ?? '', style: widget.style.titleTextStyle, ); } + if (widget.showNotes) { return FlowyTooltip( message: LocaleKeys.board_notesTooltip.tr(), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart index 4cfcfb2ddb321..8345cadc59310 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; @@ -20,26 +21,7 @@ class DesktopGridTextCellSkin extends IEditableTextCellSkin { padding: GridSize.cellContentInsets, child: Row( children: [ - BlocBuilder( - buildWhen: (p, c) => p.emoji != c.emoji, - builder: (context, state) { - if (state.emoji.isEmpty) { - return const SizedBox.shrink(); - } - return Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - FlowyText( - state.emoji, - fontSize: 16, - ), - const HSpace(6), - ], - ), - ); - }, - ), + const _IconOrEmoji(), Expanded( child: TextField( controller: textEditingController, @@ -62,3 +44,49 @@ class DesktopGridTextCellSkin extends IEditableTextCellSkin { ); } } + +class _IconOrEmoji extends StatelessWidget { + const _IconOrEmoji(); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (state.emoji != null) + ValueListenableBuilder( + valueListenable: state.emoji!, + builder: (context, value, child) { + if (value.isEmpty) { + return const SizedBox.shrink(); + } else { + return FlowyText( + value, + fontSize: 16, + ); + } + }, + ), + if (state.hasDocument != null) + ValueListenableBuilder( + valueListenable: state.hasDocument!, + builder: (context, hasDocument, child) { + if ((state.emoji?.value.isEmpty ?? true) && hasDocument) { + return FlowySvg( + FlowySvgs.notes_s, + color: Theme.of(context).hintColor, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + const HSpace(6), + ], + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart index c91ec05f79521..40e8c3531982c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart @@ -22,7 +22,7 @@ class MobileGridTextCellSkin extends IEditableTextCellSkin { buildWhen: (p, c) => p.emoji != c.emoji, builder: (context, state) => Center( child: FlowyText.emoji( - state.emoji, + state.emoji?.value ?? "", fontSize: 15, optimizeEmojiAlign: true, ), diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts index 029da3b0c9646..7993f709b73c0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts @@ -3,12 +3,13 @@ import { MoveGroupRowPayloadPB, MoveRowPayloadPB, OrderObjectPositionTypePB, + RepeatedRowIdPB, RowIdPB, UpdateRowMetaChangesetPB, } from '@/services/backend'; import { DatabaseEventCreateRow, - DatabaseEventDeleteRow, + DatabaseEventDeleteRows, DatabaseEventDuplicateRow, DatabaseEventGetRowMeta, DatabaseEventMoveGroupRow, @@ -51,13 +52,12 @@ export async function duplicateRow(viewId: string, rowId: string, groupId?: stri } export async function deleteRow(viewId: string, rowId: string, groupId?: string): Promise { - const payload = RowIdPB.fromObject({ + const payload = RepeatedRowIdPB.fromObject({ view_id: viewId, - row_id: rowId, - group_id: groupId, + row_ids: [rowId], }); - const result = await DatabaseEventDeleteRow(payload); + const result = await DatabaseEventDeleteRows(payload); return result.unwrap(); } diff --git a/frontend/rust-lib/event-integration-test/src/database_event.rs b/frontend/rust-lib/event-integration-test/src/database_event.rs index 270ac7706d60a..41757b99d16b2 100644 --- a/frontend/rust-lib/event-integration-test/src/database_event.rs +++ b/frontend/rust-lib/event-integration-test/src/database_event.rs @@ -262,7 +262,7 @@ impl EventIntegrationTest { pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB { EventBuilder::new(self.clone()) .event(DatabaseEvent::GetRow) - .payload(RowIdPB { + .payload(DatabaseViewRowIdPB { view_id: view_id.to_string(), row_id: row_id.to_string(), group_id: None, @@ -275,7 +275,7 @@ impl EventIntegrationTest { pub async fn get_row_meta(&self, view_id: &str, row_id: &str) -> RowMetaPB { EventBuilder::new(self.clone()) .event(DatabaseEvent::GetRowMeta) - .payload(RowIdPB { + .payload(DatabaseViewRowIdPB { view_id: view_id.to_string(), row_id: row_id.to_string(), group_id: None, @@ -297,7 +297,7 @@ impl EventIntegrationTest { pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option { EventBuilder::new(self.clone()) .event(DatabaseEvent::DuplicateRow) - .payload(RowIdPB { + .payload(DatabaseViewRowIdPB { view_id: view_id.to_string(), row_id: row_id.to_string(), group_id: None, diff --git a/frontend/rust-lib/event-integration-test/tests/database/local_test/test.rs b/frontend/rust-lib/event-integration-test/tests/database/local_test/test.rs index 68313ae2f0749..181a2470ebe71 100644 --- a/frontend/rust-lib/event-integration-test/tests/database/local_test/test.rs +++ b/frontend/rust-lib/event-integration-test/tests/database/local_test/test.rs @@ -288,14 +288,14 @@ async fn update_row_meta_event_with_cover_test() { // By default the row icon is None. let row = test.get_row_meta(&grid_view.id, &database.rows[0].id).await; - assert_eq!(row.cover, None); + assert_eq!(row.icon, None); // Insert cover to the row. let changeset = UpdateRowMetaChangesetPB { id: database.rows[0].id.clone(), view_id: grid_view.id.clone(), - cover_url: Some("cover url".to_owned()), - icon_url: None, + icon_url: Some("cover url".to_owned()), + cover_url: None, is_document_empty: None, }; let error = test.update_row_meta(changeset).await; @@ -303,7 +303,7 @@ async fn update_row_meta_event_with_cover_test() { // Check if the icon is updated. let row = test.get_row_meta(&grid_view.id, &database.rows[0].id).await; - assert_eq!(row.cover, Some("cover url".to_owned())); + assert_eq!(row.icon, Some("cover url".to_owned())); } #[tokio::test] diff --git a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/edit_test.rs b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/edit_test.rs index c519b1470694f..ca5f1b8d137da 100644 --- a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/edit_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/edit_test.rs @@ -55,8 +55,8 @@ async fn af_cloud_sync_anon_user_document_test() { // workspace: // view: SyncDocument let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - let document_id = views[1].id.clone(); + assert_eq!(views.len(), 3); + let document_id = views[2].id.clone(); test.open_document(document_id.clone()).await; // wait all update are send to the remote diff --git a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/anon_user_test.rs b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/anon_user_test.rs index dbcfb7097f269..99b20c85a9fce 100644 --- a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/anon_user_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/anon_user_test.rs @@ -77,11 +77,11 @@ async fn migrate_anon_user_data_to_af_cloud_test() { assert_eq!(user.authenticator, AuthenticatorPB::AppFlowyCloud); let user_first_level_views = test.get_all_workspace_views().await; - assert_eq!(user_first_level_views.len(), 2); + assert_eq!(user_first_level_views.len(), 3); println!("user first level views: {:?}", user_first_level_views); let user_second_level_views = test - .get_view(&user_first_level_views[1].id) + .get_view(&user_first_level_views[2].id) .await .child_views; println!("user second level views: {:?}", user_second_level_views); @@ -95,11 +95,11 @@ async fn migrate_anon_user_data_to_af_cloud_test() { assert_eq!(anon_first_level_views.len(), 1); // the first view of user_first_level_views is the default get started view - assert_eq!(user_first_level_views.len(), 2); + assert_eq!(user_first_level_views.len(), 3); assert_ne!(anon_first_level_views[0].id, user_first_level_views[1].id); assert_eq!( anon_first_level_views[0].name, - user_first_level_views[1].name + user_first_level_views[2].name ); // check second level diff --git a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/import_af_data_folder_test.rs b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/import_af_data_folder_test.rs index 455d4db14051c..1d952af3b48e2 100644 --- a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/import_af_data_folder_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/import_af_data_folder_test.rs @@ -28,17 +28,18 @@ async fn import_appflowy_data_need_migration_test() { .unwrap(); // after import, the structure is: // workspace: - // view: Getting Started + // view: Generate + // view: Shared // view: 037_local // view: Getting Started // view: Document1 // view: Document2 let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - assert_eq!(views[1].name, import_container_name); + assert_eq!(views.len(), 3); + assert_eq!(views[2].name, import_container_name); - let child_views = test.get_view(&views[1].id).await.child_views; + let child_views = test.get_view(&views[2].id).await.child_views; assert_eq!(child_views.len(), 1); let child_views = test.get_view(&child_views[0].id).await.child_views; @@ -81,13 +82,13 @@ async fn import_appflowy_data_folder_into_new_view_test() { // view: Grid1 // view: Grid2 let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - assert_eq!(views[1].name, import_container_name); + assert_eq!(views.len(), 3); + assert_eq!(views[2].name, import_container_name); // the 040_local should be an empty document, so try to get the document data - let _ = test.get_document_data(&views[1].id).await; + let _ = test.get_document_data(&views[2].id).await; - let local_child_views = test.get_view(&views[1].id).await.child_views; + let local_child_views = test.get_view(&views[2].id).await.child_views; assert_eq!(local_child_views.len(), 1); assert_eq!(local_child_views[0].name, "Document1"); @@ -139,16 +140,17 @@ async fn import_appflowy_data_folder_into_current_workspace_test() { .unwrap(); // after import, the structure is: // workspace: - // view: Getting Started + // view: General + // view: Shared // view: Document1 // view: Document2 // view: Grid1 // view: Grid2 let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - assert_eq!(views[1].name, "Document1"); + assert_eq!(views.len(), 3); + assert_eq!(views[2].name, "Document1"); - let document_1_child_views = test.get_view(&views[1].id).await.child_views; + let document_1_child_views = test.get_view(&views[2].id).await.child_views; assert_eq!(document_1_child_views.len(), 1); assert_eq!(document_1_child_views[0].name, "Document2"); @@ -179,9 +181,9 @@ async fn import_appflowy_data_folder_into_new_view_test2() { .unwrap(); let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - assert_eq!(views[1].name, import_container_name); - assert_040_local_2_import_content(&test, &views[1].id).await; + assert_eq!(views.len(), 3); + assert_eq!(views[2].name, import_container_name); + assert_040_local_2_import_content(&test, &views[2].id).await; drop(cleaner); } @@ -226,13 +228,14 @@ async fn import_appflowy_data_folder_multiple_times_test() { .await .unwrap(); // after import, the structure is: - // Getting Started + // General + // Shared // 040_local_2 let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 2); - assert_eq!(views[1].name, import_container_name); - assert_040_local_2_import_content(&test, &views[1].id).await; + assert_eq!(views.len(), 3); + assert_eq!(views[2].name, import_container_name); + assert_040_local_2_import_content(&test, &views[2].id).await; test .import_appflowy_data( @@ -242,16 +245,17 @@ async fn import_appflowy_data_folder_multiple_times_test() { .await .unwrap(); // after import, the structure is: - // Getting Started + // Generate + // Shared // 040_local_2 // Getting started // 040_local_2 // Getting started let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 3); - assert_eq!(views[2].name, import_container_name); - assert_040_local_2_import_content(&test, &views[1].id).await; + assert_eq!(views.len(), 4); + assert_eq!(views[3].name, import_container_name); assert_040_local_2_import_content(&test, &views[2].id).await; + assert_040_local_2_import_content(&test, &views[3].id).await; drop(cleaner); } diff --git a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs index 60f4595f53d9b..99a0f99fb0618 100644 --- a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs @@ -101,16 +101,17 @@ async fn af_cloud_open_workspace_test() { user_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; let _ = test.af_cloud_sign_up().await; - let default_document_name = "Getting started"; + let default_document_name = "General"; test.create_document("A").await; test.create_document("B").await; let first_workspace = test.get_current_workspace().await; let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 3); + assert_eq!(views.len(), 4); assert_eq!(views[0].name, default_document_name); - assert_eq!(views[1].name, "A"); - assert_eq!(views[2].name, "B"); + assert_eq!(views[1].name, "Shared"); + assert_eq!(views[2].name, "A"); + assert_eq!(views[3].name, "B"); let user_workspace = test.create_workspace("second workspace").await; test.open_workspace(&user_workspace.workspace_id).await; @@ -119,10 +120,11 @@ async fn af_cloud_open_workspace_test() { test.create_document("D").await; let views = test.get_all_workspace_views().await; - assert_eq!(views.len(), 3); + assert_eq!(views.len(), 4); assert_eq!(views[0].name, default_document_name); - assert_eq!(views[1].name, "C"); - assert_eq!(views[2].name, "D"); + assert_eq!(views[1].name, "Shared"); + assert_eq!(views[2].name, "C"); + assert_eq!(views[3].name, "D"); // simulate open workspace and check if the views are correct for i in 0..10 { @@ -144,14 +146,16 @@ async fn af_cloud_open_workspace_test() { test.open_workspace(&first_workspace.id).await; let views_1 = test.get_all_workspace_views().await; assert_eq!(views_1[0].name, default_document_name); - assert_eq!(views_1[1].name, "A"); - assert_eq!(views_1[2].name, "B"); + assert_eq!(views_1[1].name, "Shared"); + assert_eq!(views_1[2].name, "A"); + assert_eq!(views_1[3].name, "B"); test.open_workspace(&second_workspace.id).await; let views_2 = test.get_all_workspace_views().await; assert_eq!(views_2[0].name, default_document_name); - assert_eq!(views_2[1].name, "C"); - assert_eq!(views_2[2].name, "D"); + assert_eq!(views_2[1].name, "Shared"); + assert_eq!(views_2[2].name, "C"); + assert_eq!(views_2[3].name, "D"); } #[tokio::test] @@ -174,7 +178,7 @@ async fn af_cloud_different_open_same_workspace_test() { let views = client.get_all_workspace_views().await; // only the getting started view should be present - assert_eq!(views.len(), 1); + assert_eq!(views.len(), 2); for view in views { client.delete_view(&view.id).await; } @@ -205,7 +209,7 @@ async fn af_cloud_different_open_same_workspace_test() { client.open_workspace(iter_workspace_id).await; if iter_workspace_id == &cloned_shared_workspace_id { let views = client.get_all_workspace_views().await; - assert_eq!(views.len(), 1); + assert_eq!(views.len(), 2); sleep(Duration::from_millis(300)).await; } else { let views = client.get_all_workspace_views().await; @@ -242,6 +246,6 @@ async fn af_cloud_different_open_same_workspace_test() { let folder_workspace_id = folder.get_workspace_id(); assert_eq!(folder_workspace_id, Some(shared_workspace_id)); - assert_eq!(views.len(), 1, "only get: {:?}", views); // Expecting two views. - assert_eq!(views[0].name, "Getting started"); + assert_eq!(views.len(), 2, "only get: {:?}", views); // Expecting two views. + assert_eq!(views[0].name, "General"); } diff --git a/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs index 3d718647468fa..c639016acd316 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs @@ -62,9 +62,6 @@ pub struct RowMetaPB { pub icon: Option, #[pb(index = 4, one_of)] - pub cover: Option, - - #[pb(index = 5, one_of)] pub is_document_empty: Option, } @@ -80,7 +77,6 @@ impl From for RowMetaPB { id: data.id.into_inner(), document_id: None, icon: None, - cover: None, is_document_empty: None, } } @@ -92,7 +88,6 @@ impl From for RowMetaPB { id: data.id.into_inner(), document_id: None, icon: None, - cover: None, is_document_empty: None, } } @@ -104,7 +99,6 @@ impl From for RowMetaPB { id: row_detail.row.id.to_string(), document_id: Some(row_detail.document_id), icon: row_detail.meta.icon_url, - cover: row_detail.meta.cover_url, is_document_empty: Some(row_detail.meta.is_document_empty), } } @@ -305,7 +299,7 @@ impl From for UpdatedRowPB { } #[derive(Debug, Default, Clone, ProtoBuf)] -pub struct RowIdPB { +pub struct DatabaseViewRowIdPB { #[pb(index = 1)] pub view_id: String, @@ -322,7 +316,7 @@ pub struct RowIdParams { pub group_id: Option, } -impl TryInto for RowIdPB { +impl TryInto for DatabaseViewRowIdPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index 43f5010c3b7ea..fd1a7bf276a73 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -404,7 +404,7 @@ pub(crate) async fn move_field_handler( // #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_row_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, ) -> DataResult { let manager = upgrade_manager(manager)?; @@ -420,7 +420,7 @@ pub(crate) async fn get_row_handler( } pub(crate) async fn init_row_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, ) -> Result<(), FlowyError> { let manager = upgrade_manager(manager)?; @@ -433,7 +433,7 @@ pub(crate) async fn init_row_handler( } pub(crate) async fn get_row_meta_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, ) -> DataResult { let manager = upgrade_manager(manager)?; @@ -487,7 +487,7 @@ pub(crate) async fn delete_rows_handler( #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn duplicate_row_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, ) -> Result<(), FlowyError> { let manager = upgrade_manager(manager)?; @@ -960,7 +960,7 @@ pub(crate) async fn get_no_date_calendar_events_handler( #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_calendar_event_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, ) -> DataResult { let manager = upgrade_manager(manager)?; diff --git a/frontend/rust-lib/flowy-database2/src/event_map.rs b/frontend/rust-lib/flowy-database2/src/event_map.rs index bbe01c2fe1186..ae3ecc649ab0a 100644 --- a/frontend/rust-lib/flowy-database2/src/event_map.rs +++ b/frontend/rust-lib/flowy-database2/src/event_map.rs @@ -14,7 +14,6 @@ pub fn init(database_manager: Weak) -> AFPlugin { .state(database_manager); plugin .event(DatabaseEvent::GetDatabase, get_database_data_handler) - .event(DatabaseEvent::GetAllRows, get_all_rows_handler) .event(DatabaseEvent::GetDatabaseData, get_database_data_handler) .event(DatabaseEvent::GetDatabaseId, get_database_id_handler) .event(DatabaseEvent::GetDatabaseSetting, get_database_setting_handler) @@ -224,19 +223,19 @@ pub enum DatabaseEvent { /// [GetRow] event is used to get the row data,[RowPB]. [OptionalRowPB] is a wrapper that enables /// to return a nullable row data. - #[event(input = "RowIdPB", output = "OptionalRowPB")] + #[event(input = "DatabaseViewRowIdPB", output = "OptionalRowPB")] GetRow = 51, #[event(input = "RepeatedRowIdPB")] DeleteRows = 52, - #[event(input = "RowIdPB")] + #[event(input = "DatabaseViewRowIdPB")] DuplicateRow = 53, #[event(input = "MoveRowPayloadPB")] MoveRow = 54, - #[event(input = "RowIdPB", output = "RowMetaPB")] + #[event(input = "DatabaseViewRowIdPB", output = "RowMetaPB")] GetRowMeta = 55, #[event(input = "UpdateRowMetaChangesetPB")] @@ -321,7 +320,7 @@ pub enum DatabaseEvent { )] GetNoDateCalendarEvents = 124, - #[event(input = "RowIdPB", output = "CalendarEventPB")] + #[event(input = "DatabaseViewRowIdPB", output = "CalendarEventPB")] GetCalendarEvent = 125, #[event(input = "MoveCalendarEventPB")] @@ -381,12 +380,9 @@ pub enum DatabaseEvent { #[event(input = "TranslateRowPB")] TranslateRow = 175, - #[event(input = "RowIdPB")] + #[event(input = "DatabaseViewRowIdPB")] InitRow = 176, - #[event(input = "DatabaseViewIdPB", output = "RepeatedRowMetaPB")] - GetAllRows = 177, - #[event(input = "DatabaseViewIdPB", output = "DatabaseExportDataPB")] ExportRawDatabaseData = 178, } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index c10cd1204512a..cb99bedb0b7c5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -750,7 +750,6 @@ impl DatabaseEditor { id: row_id.clone().into_inner(), document_id: Some(row_document_id), icon: row_meta.icon_url, - cover: row_meta.cover_url, is_document_empty: Some(row_meta.is_document_empty), }) } else { @@ -1522,6 +1521,7 @@ impl DatabaseEditor { Ok(type_option.database_id) } + /// TODO(nathan): lazy load database rows pub async fn get_related_rows( &self, row_ids: Option<&Vec>, From 6ce77aea3e6c770dbe4ede9f9eac267843d3ff08 Mon Sep 17 00:00:00 2001 From: Mohammad Zolfaghari Date: Sun, 1 Sep 2024 11:36:58 +0330 Subject: [PATCH 18/60] fix: wrap checklist item text (#6145) --- .../database/widgets/cell_editor/checklist_cell_textfield.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_textfield.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_textfield.dart index abd519f31d1fb..9d013402268c2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_textfield.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_textfield.dart @@ -61,6 +61,7 @@ class ChecklistCellTextfield extends StatelessWidget { controller: textController, focusNode: focusNode, style: Theme.of(context).textTheme.bodyMedium, + maxLines: null, decoration: InputDecoration( border: InputBorder.none, isCollapsed: true, From 1cc41c10c4979a243dae542edc1a3b6dfc68a415 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:48:07 +0800 Subject: [PATCH 19/60] fix: local ai enable/disable (#6151) * chore: local ai state * chore: fix local ai state when switch workspace * chore: fix test --- .github/workflows/flutter_ci.yaml | 2 +- .github/workflows/rust_ci.yaml | 27 +++++++- frontend/rust-lib/flowy-ai-pub/src/cloud.rs | 6 ++ frontend/rust-lib/flowy-ai/src/ai_manager.rs | 6 +- .../flowy-ai/src/local_ai/local_llm_chat.rs | 69 ++++++++++++++++--- .../src/middleware/chat_service_mw.rs | 9 ++- .../flowy-core/src/integrate/trait_impls.rs | 13 +++- .../rust-lib/flowy-core/src/integrate/user.rs | 1 + .../rust-lib/flowy-database2/src/manager.rs | 6 +- .../src/services/database/database_editor.rs | 2 +- .../flowy-server/src/af_cloud/impls/chat.rs | 14 +++- .../af_cloud/impls/user/cloud_service_impl.rs | 1 - .../rust-lib/flowy-server/src/default_impl.rs | 11 +++ 13 files changed, 144 insertions(+), 23 deletions(-) diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 580e6f176dca6..761e75f6b00de 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -246,7 +246,7 @@ jobs: - name: Run Docker-Compose working-directory: AppFlowy-Cloud env: - BACKEND_VERSION: 0.6.4-amd64 + APPFLOWY_CLOUD_VERSION: 0.6.4-amd64 run: | if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then docker compose pull diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 4f58c5fb2db2a..e6b7017023e81 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -43,6 +43,31 @@ jobs: sed -i '' 's|RUST_LOG=.*|RUST_LOG=trace|' .env sed -i '' 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env + - name: Ensure AppFlowy-Cloud is Running with Correct Version + working-directory: AppFlowy-Cloud + env: + APPFLOWY_CLOUD_VERSION: 0.6.4-amd64 + run: | + container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q) + if [ -z "$container_id" ]; then + echo "AppFlowy-Cloud container is not running. Pulling and starting the container..." + docker compose pull + docker compose up -d + echo "Waiting for the container to be ready..." + sleep 10 + else + running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id") + if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then + echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..." + docker compose pull + docker compose up -d + echo "Waiting for the container to be ready..." + sleep 10 + else + echo "AppFlowy-Cloud is running with the correct version." + fi + fi + - name: Run rust-lib tests working-directory: frontend/rust-lib env: @@ -107,7 +132,7 @@ jobs: - name: Run Docker-Compose working-directory: AppFlowy-Cloud env: - BACKEND_VERSION: 0.6.4-amd64 + APPFLOWY_CLOUD_VERSION: 0.6.4-amd64 run: | if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then docker compose pull diff --git a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs index 918477b634fc6..503bb663d9b8d 100644 --- a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs @@ -3,6 +3,7 @@ pub use client_api::entity::ai_dto::{ AppFlowyOfflineAI, CompletionType, CreateTextChatContext, LLMModel, LocalAIConfig, ModelInfo, RelatedQuestion, RepeatedRelatedQuestion, StringOrMessage, }; +pub use client_api::entity::billing_dto::SubscriptionPlan; pub use client_api::entity::{ ChatAuthorType, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatMetadataContentType, ChatMetadataData, MessageCursor, QAChatMessage, QuestionStreamValue, RepeatedChatMessage, @@ -98,4 +99,9 @@ pub trait ChatCloudService: Send + Sync + 'static { ) -> Result<(), FlowyError> { Ok(()) } + + async fn get_workspace_plan( + &self, + workspace_id: &str, + ) -> Result, FlowyError>; } diff --git a/frontend/rust-lib/flowy-ai/src/ai_manager.rs b/frontend/rust-lib/flowy-ai/src/ai_manager.rs index 8b3bf782a1120..947ec7f857bb9 100644 --- a/frontend/rust-lib/flowy-ai/src/ai_manager.rs +++ b/frontend/rust-lib/flowy-ai/src/ai_manager.rs @@ -92,11 +92,7 @@ impl AIManager { pub async fn close_chat(&self, chat_id: &str) -> Result<(), FlowyError> { trace!("close chat: {}", chat_id); - - if self.local_ai_controller.is_running() { - info!("[AI Plugin] notify close chat: {}", chat_id); - self.local_ai_controller.close_chat(chat_id); - } + self.local_ai_controller.close_chat(chat_id); Ok(()) } diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs index 12466af8caa47..8155b7587262d 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs @@ -8,7 +8,7 @@ use appflowy_plugin::manager::PluginManager; use appflowy_plugin::util::is_apple_silicon; use flowy_ai_pub::cloud::{ AppFlowyOfflineAI, ChatCloudService, ChatMessageMetadata, ChatMetadataContentType, LLMModel, - LocalAIConfig, + LocalAIConfig, SubscriptionPlan, }; use flowy_error::{FlowyError, FlowyResult}; use flowy_sqlite::kv::KVStorePreferences; @@ -26,7 +26,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::select; use tokio_stream::StreamExt; -use tracing::{debug, error, info, instrument, trace}; +use tracing::{debug, error, info, instrument, trace, warn}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LLMSetting { @@ -50,6 +50,7 @@ pub struct LocalAIController { current_chat_id: ArcSwapOption, store_preferences: Arc, user_service: Arc, + cloud_service: Arc, } impl Deref for LocalAIController { @@ -70,7 +71,7 @@ impl LocalAIController { let local_ai = Arc::new(AppFlowyLocalAI::new(plugin_manager)); let res_impl = LLMResourceServiceImpl { user_service: user_service.clone(), - cloud_service, + cloud_service: cloud_service.clone(), store_preferences: store_preferences.clone(), }; @@ -107,6 +108,7 @@ impl LocalAIController { current_chat_id, store_preferences, user_service, + cloud_service, }; let rag_enabled = this.is_rag_enabled(); @@ -151,6 +153,33 @@ impl LocalAIController { pub async fn refresh(&self) -> FlowyResult<()> { let is_enabled = self.is_enabled(); self.enable_chat_plugin(is_enabled).await?; + + if is_enabled { + let local_ai = self.local_ai.clone(); + let workspace_id = self.user_service.workspace_id()?; + let cloned_service = self.cloud_service.clone(); + let store_preferences = self.store_preferences.clone(); + tokio::spawn(async move { + let key = local_ai_enabled_key(&workspace_id); + match cloned_service.get_workspace_plan(&workspace_id).await { + Ok(plans) => { + trace!("[AI Plugin] workspace:{} plans: {:?}", workspace_id, plans); + if !plans.contains(&SubscriptionPlan::AiLocal) { + info!( + "disable local ai plugin for workspace: {}. reason: no plan found", + workspace_id + ); + let _ = store_preferences.set_bool(&key, false); + let _ = local_ai.destroy_chat_plugin().await; + } + }, + Err(err) => { + warn!("[AI Plugin]: failed to get workspace plan: {:?}", err); + }, + } + }); + } + Ok(()) } @@ -165,23 +194,27 @@ impl LocalAIController { /// Indicate whether the local AI plugin is running. pub fn is_running(&self) -> bool { + if !self.is_enabled() { + return false; + } self.local_ai.get_plugin_running_state().is_ready() } /// Indicate whether the local AI is enabled. + /// AppFlowy store the value in local storage isolated by workspace id. Each workspace can have + /// different settings. pub fn is_enabled(&self) -> bool { - if let Ok(key) = self.local_ai_enabled_key() { + if let Ok(key) = self + .user_service + .workspace_id() + .map(|workspace_id| local_ai_enabled_key(&workspace_id)) + { self.store_preferences.get_bool(&key).unwrap_or(true) } else { false } } - fn local_ai_enabled_key(&self) -> FlowyResult { - let workspace_id = self.user_service.workspace_id()?; - Ok(format!("{}:{}", APPFLOWY_LOCAL_AI_ENABLED, workspace_id)) - } - /// Indicate whether the local AI chat is enabled. In the future, we can support multiple /// AI plugin. pub fn is_chat_enabled(&self) -> bool { @@ -225,6 +258,10 @@ impl LocalAIController { } pub fn close_chat(&self, chat_id: &str) { + if !self.is_running() { + return; + } + info!("[AI Plugin] notify close chat: {}", chat_id); let weak_ctrl = Arc::downgrade(&self.local_ai); let chat_id = chat_id.to_string(); tokio::spawn(async move { @@ -285,6 +322,13 @@ impl LocalAIController { } pub fn get_chat_plugin_state(&self) -> LocalAIPluginStatePB { + if !self.is_enabled() { + return LocalAIPluginStatePB { + state: RunningStatePB::Stopped, + offline_ai_ready: false, + }; + } + let offline_ai_ready = self.local_ai_resource.is_offline_app_ready(); let state = self.local_ai.get_plugin_running_state(); LocalAIPluginStatePB { @@ -317,7 +361,8 @@ impl LocalAIController { } pub async fn toggle_local_ai(&self) -> FlowyResult { - let key = self.local_ai_enabled_key()?; + let workspace_id = self.user_service.workspace_id()?; + let key = local_ai_enabled_key(&workspace_id); let enabled = !self.store_preferences.get_bool(&key).unwrap_or(true); self.store_preferences.set_bool(&key, enabled)?; @@ -552,3 +597,7 @@ impl LLMResourceService for LLMResourceServiceImpl { .get_bool_or_default(APPFLOWY_LOCAL_AI_CHAT_RAG_ENABLED) } } + +fn local_ai_enabled_key(workspace_id: &str) -> String { + format!("{}:{}", APPFLOWY_LOCAL_AI_ENABLED, workspace_id) +} diff --git a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs index c2e46b3a800c9..f3ecb4e70333c 100644 --- a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs +++ b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, CompletionType, CreateTextChatContext, LocalAIConfig, MessageCursor, RelatedQuestion, RepeatedChatMessage, - RepeatedRelatedQuestion, StreamAnswer, StreamComplete, + RepeatedRelatedQuestion, StreamAnswer, StreamComplete, SubscriptionPlan, }; use flowy_error::{FlowyError, FlowyResult}; use futures::{stream, Sink, StreamExt, TryStreamExt}; @@ -323,4 +323,11 @@ impl ChatCloudService for AICloudServiceMiddleware { .await } } + + async fn get_workspace_plan( + &self, + workspace_id: &str, + ) -> Result, FlowyError> { + self.cloud_service.get_workspace_plan(workspace_id).await + } } diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index 9c3d97c067500..d21cc981a55ce 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -22,7 +22,7 @@ use collab_integrate::collab_builder::{ }; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, LocalAIConfig, MessageCursor, - RepeatedChatMessage, StreamAnswer, StreamComplete, + RepeatedChatMessage, StreamAnswer, StreamComplete, SubscriptionPlan, }; use flowy_database_pub::cloud::{ DatabaseAIService, DatabaseCloudService, DatabaseSnapshot, EncodeCollabByOid, SummaryRowContent, @@ -693,6 +693,17 @@ impl ChatCloudService for ServerProvider { .get_local_ai_config(workspace_id) .await } + + async fn get_workspace_plan( + &self, + workspace_id: &str, + ) -> Result, FlowyError> { + self + .get_server()? + .chat_service() + .get_workspace_plan(workspace_id) + .await + } } #[async_trait] diff --git a/frontend/rust-lib/flowy-core/src/integrate/user.rs b/frontend/rust-lib/flowy-core/src/integrate/user.rs index a39ddb3945543..9ac347bb049c4 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/user.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/user.rs @@ -70,6 +70,7 @@ impl UserStatusCallback for UserStatusCallbackImpl { .initialize(user_id, authenticator == &Authenticator::Local) .await?; self.document_manager.initialize(user_id).await?; + self.ai_manager.initialize(&user_workspace.id).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-database2/src/manager.rs b/frontend/rust-lib/flowy-database2/src/manager.rs index 344a1516d78dc..5280e07e42792 100644 --- a/frontend/rust-lib/flowy-database2/src/manager.rs +++ b/frontend/rust-lib/flowy-database2/src/manager.rs @@ -308,11 +308,15 @@ impl DatabaseManager { if let Some(editor) = editors.get(&database_id) { editor.close_view(view_id).await; // when there is no opening views, mark the database to be removed. + trace!( + "{} has {} opening views", + database_id, + editor.num_of_opening_views().await + ); should_remove = editor.num_of_opening_views().await == 0; } if should_remove { - trace!("remove database editor:{}", database_id); if let Some(editor) = editors.remove(&database_id) { editor.close_database().await; self diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index cb99bedb0b7c5..fc1facf0b2e64 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -1300,7 +1300,7 @@ impl DatabaseEditor { } pub async fn close_database(&self) { - info!("Close database: {}", self.database_id); + info!("close database editor: {}", self.database_id); let cancellation = self.database_cancellation.read().await; if let Some(cancellation) = &*cancellation { info!("Cancel database operation"); diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs index 67df702860cdc..b9e7433c0f459 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs @@ -8,7 +8,7 @@ use client_api::entity::{ }; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, LocalAIConfig, StreamAnswer, - StreamComplete, + StreamComplete, SubscriptionPlan, }; use flowy_error::FlowyError; use futures_util::{StreamExt, TryStreamExt}; @@ -217,4 +217,16 @@ where .await?; Ok(()) } + + async fn get_workspace_plan( + &self, + workspace_id: &str, + ) -> Result, FlowyError> { + let plans = self + .inner + .try_get_client()? + .get_active_workspace_subscriptions(workspace_id) + .await?; + Ok(plans) + } } diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs index 5df9dce1dd5e0..a1dafe578baaa 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs @@ -506,7 +506,6 @@ where &self, workspace_id: String, ) -> Result, FlowyError> { - let workspace_id = workspace_id.to_string(); let try_get_client = self.server.try_get_client(); let client = try_get_client?; let plans = client diff --git a/frontend/rust-lib/flowy-server/src/default_impl.rs b/frontend/rust-lib/flowy-server/src/default_impl.rs index ea41746424f87..33557b2d53ff4 100644 --- a/frontend/rust-lib/flowy-server/src/default_impl.rs +++ b/frontend/rust-lib/flowy-server/src/default_impl.rs @@ -2,6 +2,7 @@ use client_api::entity::ai_dto::{CompletionType, LocalAIConfig, RepeatedRelatedQ use client_api::entity::{ChatMessageType, MessageCursor, RepeatedChatMessage}; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, StreamAnswer, StreamComplete, + SubscriptionPlan, }; use flowy_error::FlowyError; use lib_infra::async_trait::async_trait; @@ -106,4 +107,14 @@ impl ChatCloudService for DefaultChatCloudServiceImpl { .with_context("Get local ai config is not supported in local server."), ) } + + async fn get_workspace_plan( + &self, + _workspace_id: &str, + ) -> Result, FlowyError> { + Err( + FlowyError::not_support() + .with_context("Get local ai config is not supported in local server."), + ) + } } From 080b7db60545c7ecbfdb49fed2b18dfb2c9ac1cd Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 2 Sep 2024 10:46:51 +0800 Subject: [PATCH 20/60] chore: remove unused translation (#6158) --- .../presentation/settings/pages/settings_plan_view.dart | 9 ++------- frontend/resources/translations/en.json | 3 +-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart index 135259a04ed43..d64790527e33f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart @@ -1,7 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/util/int64_extension.dart'; @@ -22,6 +20,7 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; @@ -121,11 +120,7 @@ class _SettingsPlanViewState extends State { priceInfo: LocaleKeys .settings_planPage_planUsage_addons_aiMax_priceInfo .tr(), - recommend: LocaleKeys - .settings_planPage_planUsage_addons_aiMax_recommend - .tr( - args: [SubscriptionPlanPB.AiMax.priceMonthBilling], - ), + recommend: '', buttonText: state.subscriptionInfo.hasAIMax ? LocaleKeys .settings_planPage_planUsage_addons_activeLabel diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index e24aaa09250d3..1a3914fb97f14 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -747,8 +747,7 @@ "title": "AI Max", "description": "Unlimited AI responses powered by GPT-4o, Claude 3.5 Sonnet, and more", "price": "{}", - "priceInfo": "Per user per month billed annually", - "recommend": "" + "priceInfo": "Per user per month billed annually" }, "aiOnDevice": { "title": "AI On-device for Mac", From 08bf5db2de423769466cfb3e03482c133688704e Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:54:21 +0800 Subject: [PATCH 21/60] chore: auto update field title when creating a new field (#6159) * chore: auto update field title when creating a new field * chore: fix test --- .github/workflows/flutter_ci.yaml | 16 ++++++++++++++-- .github/workflows/rust_ci.yaml | 18 +++++++++++++++--- .../field/mobile_quick_field_editor.dart | 1 + .../application/cell/bloc/text_cell_bloc.dart | 11 +++-------- .../application/field/field_editor_bloc.dart | 15 ++++++++++++++- .../plugins/database/domain/field_service.dart | 8 ++++++++ .../widgets/header/desktop_field_cell.dart | 1 + .../database/widgets/field/field_editor.dart | 3 +++ .../database/widgets/row/row_property.dart | 3 +++ .../widgets/setting/setting_property_list.dart | 1 + .../board_test/create_or_edit_field_test.dart | 1 + .../test/bloc_test/board_test/util.dart | 1 + .../grid_test/field/edit_field_test.dart | 1 + .../test/bloc_test/grid_test/util.dart | 1 + .../src/database_event.rs | 1 + .../src/entities/field_entities.rs | 5 +++++ .../flowy-database2/src/event_handler.rs | 7 ++++++- .../src/services/database/database_editor.rs | 2 ++ .../tests/database/field_test/script.rs | 2 +- 19 files changed, 82 insertions(+), 16 deletions(-) diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 761e75f6b00de..9b9725a09f567 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -248,12 +248,24 @@ jobs: env: APPFLOWY_CLOUD_VERSION: 0.6.4-amd64 run: | - if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then + container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q) + if [ -z "$container_id" ]; then + echo "AppFlowy-Cloud container is not running. Pulling and starting the container..." docker compose pull docker compose up -d + echo "Waiting for the container to be ready..." sleep 10 else - echo "Docker container 'appflowy-cloud' is already running." + running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id") + if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then + echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..." + docker compose pull + docker compose up -d + echo "Waiting for the container to be ready..." + sleep 10 + else + echo "AppFlowy-Cloud is running with the correct version." + fi fi - name: Checkout source code diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index e6b7017023e81..566aef3b7b281 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -129,17 +129,29 @@ jobs: sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env - - name: Run Docker-Compose + - name: Ensure AppFlowy-Cloud is Running with Correct Version working-directory: AppFlowy-Cloud env: APPFLOWY_CLOUD_VERSION: 0.6.4-amd64 run: | - if [ "$(docker ps --filter name=appflowy-cloud -q)" == "" ]; then + container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q) + if [ -z "$container_id" ]; then + echo "AppFlowy-Cloud container is not running. Pulling and starting the container..." docker compose pull docker compose up -d + echo "Waiting for the container to be ready..." sleep 10 else - echo "Docker container 'appflowy-cloud' is already running." + running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id") + if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then + echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..." + docker compose pull + docker compose up -d + echo "Waiting for the container to be ready..." + sleep 10 + else + echo "AppFlowy-Cloud is running with the correct version." + fi fi - name: Run rust-lib tests diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart index 0b415b04a67bd..54448057089fc 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart @@ -62,6 +62,7 @@ class _QuickEditFieldState extends State { viewId: widget.viewId, fieldController: widget.fieldController, field: widget.fieldInfo.field, + isNew: false, ), child: BlocConsumer( listenWhen: (previous, current) => diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart index dab09c02f78a2..700cd86cc2936 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart @@ -43,9 +43,6 @@ class TextCellBloc extends Bloc { emit(state.copyWith(wrap: wrap)); } }, - didUpdateEmoji: (String emoji, bool hasDocument) { - // emit(state.copyWith(emoji: emoji, hasDocument: hasDocument)); - }, updateText: (String text) { if (state.content != text) { cellController.saveCellData(text, debounce: true); @@ -85,10 +82,6 @@ class TextCellEvent with _$TextCellEvent { _DidUpdateField; const factory TextCellEvent.updateText(String text) = _UpdateText; const factory TextCellEvent.enableEdit(bool enabled) = _EnableEdit; - const factory TextCellEvent.didUpdateEmoji( - String emoji, - bool hasDocument, - ) = _UpdateEmoji; } @freezed @@ -105,15 +98,17 @@ class TextCellState with _$TextCellState { final cellData = cellController.getCellData() ?? ""; final wrap = cellController.fieldInfo.wrapCellContent ?? true; ValueNotifier? emoji; + ValueNotifier? hasDocument; if (cellController.fieldInfo.isPrimary) { emoji = cellController.icon; + hasDocument = cellController.hasDocument; } return TextCellState( content: cellData, emoji: emoji, enableEdit: false, - hasDocument: cellController.hasDocument, + hasDocument: hasDocument, wrap: wrap, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart index b33f9f44eedb4..a4748c0143dbc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/domain/field_settings_service.dart'; +import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; @@ -20,6 +21,7 @@ class FieldEditorBloc extends Bloc { required this.fieldController, this.onFieldInserted, required FieldPB field, + required this.isNew, }) : fieldId = field.id, fieldService = FieldBackendService( viewId: viewId, @@ -34,6 +36,7 @@ class FieldEditorBloc extends Bloc { final String viewId; final String fieldId; + final bool isNew; final FieldController fieldController; final FieldBackendService fieldService; final FieldSettingsBackendService fieldSettingsService; @@ -58,11 +61,20 @@ class FieldEditorBloc extends Bloc { emit(state.copyWith(field: fieldInfo)); }, switchFieldType: (fieldType) async { - await fieldService.updateType(fieldType: fieldType); + String? fieldName; + if (!state.wasRenameManually && isNew) { + fieldName = fieldType.i18n; + } + + await fieldService.updateType( + fieldType: fieldType, + fieldName: fieldName, + ); }, renameField: (newName) async { final result = await fieldService.updateField(name: newName); _logIfError(result); + emit(state.copyWith(wasRenameManually: true)); }, updateTypeOption: (typeOptionData) async { final result = await FieldBackendService.updateFieldTypeOption( @@ -164,5 +176,6 @@ class FieldEditorEvent with _$FieldEditorEvent { class FieldEditorState with _$FieldEditorState { const factory FieldEditorState({ required final FieldInfo field, + @Default(false) bool wasRenameManually, }) = _FieldEditorState; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/field_service.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/field_service.dart index 9bc873f7f1e78..fe23916d2df8e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/field_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/field_service.dart @@ -110,12 +110,18 @@ class FieldBackendService { required String viewId, required String fieldId, required FieldType fieldType, + String? fieldName, }) { final payload = UpdateFieldTypePayloadPB() ..viewId = viewId ..fieldId = fieldId ..fieldType = fieldType; + // Only set if fieldName is not null + if (fieldName != null) { + payload.fieldName = fieldName; + } + return DatabaseEventUpdateFieldType(payload).send(); } @@ -177,11 +183,13 @@ class FieldBackendService { Future> updateType({ required FieldType fieldType, + String? fieldName, }) => updateFieldType( viewId: viewId, fieldId: fieldId, fieldType: fieldType, + fieldName: fieldName, ); Future> delete() => diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart index d127ef6f912ec..b14643381db14 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart @@ -87,6 +87,7 @@ class _GridFieldCellState extends State { viewId: widget.viewId, fieldController: widget.fieldController, field: widget.fieldInfo.field, + isNewField: widget.isNew, initialPage: widget.isNew ? FieldEditorPage.details : FieldEditorPage.general, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart index 8ff2ccea13c85..e0d040ea6498e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart @@ -33,6 +33,7 @@ class FieldEditor extends StatefulWidget { required this.viewId, required this.field, required this.fieldController, + required this.isNewField, this.initialPage = FieldEditorPage.details, this.onFieldInserted, }); @@ -42,6 +43,7 @@ class FieldEditor extends StatefulWidget { final FieldController fieldController; final FieldEditorPage initialPage; final void Function(String fieldId)? onFieldInserted; + final bool isNewField; @override State createState() => _FieldEditorState(); @@ -72,6 +74,7 @@ class _FieldEditorState extends State { field: widget.field, fieldController: widget.fieldController, onFieldInserted: widget.onFieldInserted, + isNew: widget.isNewField, ), child: _currentPage == FieldEditorPage.details ? _fieldDetails() diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart index 60dc940cea45c..6dd81fc501246 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart @@ -153,6 +153,7 @@ class _PropertyCellState extends State<_PropertyCell> { .getField(widget.cellContext.fieldId)! .field, fieldController: widget.fieldController, + isNewField: false, ), child: ValueListenableBuilder( valueListenable: _isFieldHover, @@ -230,6 +231,7 @@ class _PropertyCellState extends State<_PropertyCell> { viewId: widget.fieldController.viewId, field: fieldInfo.field, fieldController: widget.fieldController, + isNewField: false, ), child: SizedBox( width: 160, @@ -415,6 +417,7 @@ class _CreateRowFieldButtonState extends State { viewId: widget.viewId, field: createdField!, fieldController: widget.fieldController, + isNewField: true, ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart index f7dd93d9bc7fa..125e0b08f9e41 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart @@ -206,6 +206,7 @@ class _DatabasePropertyCellState extends State { viewId: widget.viewId, field: widget.fieldInfo.field, fieldController: widget.fieldController, + isNewField: false, ); }, ); diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart index 752ff272daedf..187fa6d5e9cde 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart @@ -36,6 +36,7 @@ void main() { viewId: context.gridView.id, field: fieldInfo.field, fieldController: context.fieldController, + isNew: false, ); await boardResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart index e29c6a4a7345e..e89f5a8815e95 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart @@ -83,6 +83,7 @@ class BoardTestContext { viewId: databaseController.viewId, fieldController: fieldController, field: fieldInfo.field, + isNew: false, ); return editorBloc; } diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart index 32b1d603d9ac9..b3a3a3b5cfbe6 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart @@ -11,6 +11,7 @@ Future createEditorBloc(AppFlowyGridTest gridTest) async { viewId: context.gridView.id, fieldController: context.fieldController, field: fieldInfo.field, + isNew: false, ); } diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index 66502b44cfdef..c652bfcb6abf9 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -103,6 +103,7 @@ Future createFieldEditor({ viewId: databaseController.viewId, fieldController: databaseController.fieldController, field: field, + isNew: true, ); }, (err) => throw Exception(err), diff --git a/frontend/rust-lib/event-integration-test/src/database_event.rs b/frontend/rust-lib/event-integration-test/src/database_event.rs index 41757b99d16b2..041849bf872f7 100644 --- a/frontend/rust-lib/event-integration-test/src/database_event.rs +++ b/frontend/rust-lib/event-integration-test/src/database_event.rs @@ -184,6 +184,7 @@ impl EventIntegrationTest { view_id: view_id.to_string(), field_id: field_id.to_string(), field_type, + field_name: None, }) .async_send() .await diff --git a/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs index 3263986db8aec..a2c35d32a84a9 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs @@ -207,12 +207,16 @@ pub struct UpdateFieldTypePayloadPB { #[pb(index = 3)] pub field_type: FieldType, + + #[pb(index = 4, one_of)] + pub field_name: Option, } pub struct EditFieldParams { pub view_id: String, pub field_id: String, pub field_type: FieldType, + pub field_name: Option, } impl TryInto for UpdateFieldTypePayloadPB { @@ -225,6 +229,7 @@ impl TryInto for UpdateFieldTypePayloadPB { view_id: view_id.0, field_id: field_id.0, field_type: self.field_type, + field_name: self.field_name, }) } } diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index fd1a7bf276a73..4c85e1502413d 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -332,7 +332,12 @@ pub(crate) async fn switch_to_field_handler( .await?; let old_field = database_editor.get_field(¶ms.field_id).await; database_editor - .switch_to_field_type(¶ms.view_id, ¶ms.field_id, params.field_type) + .switch_to_field_type( + ¶ms.view_id, + ¶ms.field_id, + params.field_type, + params.field_name, + ) .await?; if let Some(new_type_option) = database_editor diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index fc1facf0b2e64..7d42b41ffc457 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -443,6 +443,7 @@ impl DatabaseEditor { view_id: &str, field_id: &str, new_field_type: FieldType, + field_name: Option, ) -> FlowyResult<()> { let mut database = self.database.write().await; let field = database.get_field(field_id); @@ -476,6 +477,7 @@ impl DatabaseEditor { database.update_field(field_id, |update| { update .set_field_type(new_field_type.into()) + .set_name_if_not_none(field_name) .set_type_option(new_field_type.into(), Some(transformed_type_option)); }); diff --git a/frontend/rust-lib/flowy-database2/tests/database/field_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/field_test/script.rs index 64a41d869d20f..c2c347239528d 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/field_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/field_test/script.rs @@ -88,7 +88,7 @@ impl DatabaseFieldTest { // self .editor - .switch_to_field_type(&view_id, &field_id, new_field_type) + .switch_to_field_type(&view_id, &field_id, new_field_type, None) .await .unwrap(); }, From ed2f2360fe46b231d03bf9432338781923e5acb5 Mon Sep 17 00:00:00 2001 From: FreezingKas <57356272+FreezingKas@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:35:08 +0200 Subject: [PATCH 22/60] chore: update FR-fr translations (#6155) Co-authored-by: Lucas.Xu --- frontend/resources/translations/fr-FR.json | 21 +++++++++++++++++---- frontend/resources/translations/vi-VN.json | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index e83285b1d199b..044accd32b0b9 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -847,6 +847,7 @@ "itemThree": "5 Go", "itemFour": "Oui", "itemFive": "Oui", + "itemFileUpload": "Jusqu'à 7 Mo", "intelligentSearch": "Recherche intelligente" }, "proLabels": { @@ -856,6 +857,7 @@ "itemFour": "Oui", "itemFive": "Oui", "itemSix": "illimité", + "itemFileUpload": "Illimité", "intelligentSearch": "Recherche intelligente" }, "paymentSuccess": { @@ -1228,6 +1230,7 @@ "typeAValue": "Tapez une valeur...", "layout": "Mise en page", "databaseLayout": "Mise en page", + "viewList": "Vues de base de données", "editView": "Modifier vue", "boardSettings": "Paramètres du tableau", "calendarSettings": "Paramètres du calendrier", @@ -1235,8 +1238,7 @@ "duplicateView": "Dupliquer la vue", "deleteView": "Supprimer la vue", "numberOfVisibleFields": "{} affiché(s)", - "Properties": "Propriétés", - "viewList": "Vues de base de données" + "Properties": "Propriétés" }, "textFilter": { "contains": "Contient", @@ -1322,6 +1324,7 @@ "checklistFieldName": "Check-list", "relationFieldName": "Relation", "summaryFieldName": "Résume IA", + "timeFieldName": "Temps", "translateFieldName": "Traduction IA", "translateTo": "Traduire en", "numberFormat": "Format du nombre", @@ -1495,6 +1498,7 @@ "image": "Image", "bulletedList": "Liste à puces", "numberedList": "Liste numérotée", + "doc": "Doc", "linkedDoc": "Lien vers la page", "grid": "Grille", "linkedGrid": "Grille liée", @@ -2044,7 +2048,8 @@ }, "error": { "weAreSorry": "Nous sommes désolés", - "loadingViewError": "Nous rencontrons des difficultés pour charger cette vue. Veuillez vérifier votre connexion Internet, actualiser l'application et n'hésitez pas à contacter l'équipe si le problème persiste." + "loadingViewError": "Nous rencontrons des difficultés pour charger cette vue. Veuillez vérifier votre connexion Internet, actualiser l'application et n'hésitez pas à contacter l'équipe si le problème persiste.", + "syncError": "Les données ne sont pas synchronisées depuis un autre appareil" }, "editor": { "bold": "Gras", @@ -2263,6 +2268,7 @@ }, "commandPalette": { "placeholder": "Tapez pour rechercher des vues...", + "bestMatches": "Meilleurs résultats", "recentHistory": "Historique récent", "navigateHint": "naviguer", "loadingTooltip": "Nous recherchons des résultats...", @@ -2314,7 +2320,8 @@ "copy": { "codeBlock": "Le contenu du bloc de code a été copié dans le presse-papiers", "imageBlock": "Le lien de l'image a été copié dans le presse-papiers", - "mathBlock": "L'équation mathématique a été copiée dans le presse-papiers" + "mathBlock": "L'équation mathématique a été copiée dans le presse-papiers", + "fileBlock": "Le lien du fichier a été copié dans le presse-papier" }, "containsPublishedPage": "Cette page contient une ou plusieurs pages publiées. Si vous continuez, elles ne seront plus publiées. Voulez-vous procéder à la suppression ?", "publishSuccessfully": "Publié avec succès", @@ -2412,5 +2419,11 @@ "failedToAddComment": "Problème lors de l'ajout du commentaire", "commentAddedSuccessfully": "Commentaire ajouté avec succès.", "commentAddedSuccessTip": "Vous venez d'ajouter ou de répondre à un commentaire. Souhaitez-vous passer en haut de la page pour voir les derniers commentaires ?" + }, + "template": { + "category": { + "deleteCategoryDescription": "Êtes-vous sûr de vouloir supprimer cette catégorie ?", + "typeToSearch": "Tapez pour rechercher une catégorie..." + } } } diff --git a/frontend/resources/translations/vi-VN.json b/frontend/resources/translations/vi-VN.json index 5c987c14888e7..be9eeeacceaa7 100644 --- a/frontend/resources/translations/vi-VN.json +++ b/frontend/resources/translations/vi-VN.json @@ -1224,7 +1224,6 @@ "typeAValue": "Nhập một giá trị...", "layout": "Bố cục", "databaseLayout": "Bố cục", - "viewList": "Database Views", "editView": "Chỉnh sửa chế độ xem", "boardSettings": "Cài đặt bảng", "calendarSettings": "Cài đặt lịch", @@ -1232,7 +1231,8 @@ "duplicateView": "Xem trùng lặp", "deleteView": "Xóa chế độ xem", "numberOfVisibleFields": "{} đã hiển thị", - "Properties": "Thuộc tính" + "Properties": "Thuộc tính", + "viewList": "Database Views" }, "textFilter": { "contains": "Chứa", From e56177852629cfd6ae89d33ec0e20b5c2b83893e Mon Sep 17 00:00:00 2001 From: Ufal Salman <35857106+ritokatsuga@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:35:44 +0700 Subject: [PATCH 23/60] chore: update ID-id translations (#6150) Co-authored-by: Lucas.Xu --- frontend/resources/translations/id-ID.json | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/frontend/resources/translations/id-ID.json b/frontend/resources/translations/id-ID.json index 2506695bb8525..c1d90d7cd6c75 100644 --- a/frontend/resources/translations/id-ID.json +++ b/frontend/resources/translations/id-ID.json @@ -9,6 +9,7 @@ "title": "Judul", "youCanAlso": "Anda juga bisa", "and": "Dan", + "failedToOpenUrl": "Gagal membuka url: {}", "blockActions": { "addBelowTooltip": "Klik untuk menambahkan di bawah", "addAboveCmd": "Alt+klik", @@ -35,17 +36,39 @@ "loginButtonText": "Masuk", "loginStartWithAnonymous": "Mulai dengan sesi anonim", "continueAnonymousUser": "Lanjutkan dengan sesi anonim", + "anonymous": "Anonim", "buttonText": "Masuk", "signingInText": "Sedang masuk", "forgotPassword": "Lupa kata sandi?", "emailHint": "Surat elektronik", "passwordHint": "Kata sandi", "dontHaveAnAccount": "Belum punya akun?", + "createAccount": "Membuat akun", "repeatPasswordEmptyError": "Sandi ulang tidak boleh kosong", "unmatchedPasswordError": "Kata sandi konfirmasi tidak sesuai dengan kata sandi awal", "syncPromptMessage": "Menyinkronkan data mungkin memerlukan waktu beberapa saat. Mohon jangan tutup halaman ini", "or": "ATAU", + "signInWithGoogle": "Lanjutkan dengan Google", + "signInWithGithub": "Lanjutkan dengan GitHub", + "signInWithDiscord": "Lanjutkan dengan Discord", + "signInWithApple": "Lanjutkan dengan Apple", + "continueAnotherWay": "Lanjutkan dengan cara lain", + "signUpWithGoogle": "Mendaftar dengan Google", + "signUpWithGithub": "Mendaftar dengan GitHub", + "signUpWithDiscord": "Mendaftar dengan Discord", "signInWith": "Masuk dengan:", + "signInWithEmail": "Lanjutkan dengan Email", + "signInWithMagicLink": "Lanjut", + "signUpWithMagicLink": "Mendaftar dengan Magic Link", + "pleaseInputYourEmail": "Masukkan alamat email Anda", + "settings": "Pengaturan", + "magicLinkSent": "Magic Link terkirim!", + "invalidEmail": "Masukkan alamat email yang valid", + "alreadyHaveAnAccount": "Sudah memiliki akun?", + "logIn": "Masuk", + "generalError": "Ada yang salah. Mohon coba kembali nanti", + "limitRateError": "Demi keamanan, Anda hanya dapat meminta magic link setiap 60 detik", + "magicLinkSentDescription": "Magic Link sudah terkirim ke email Anda. Klik pada tautan untuk menyelesaikan proses masuk Anda. Tautan akan kedaluwarsa setelah 5 menit.", "LogInWithGoogle": "Masuk dengan Google", "LogInWithGithub": "Masuk dengan Github", "LogInWithDiscord": "Masuk dengan Discord", From dda798752fc570327dab544f9969254b6568b3f8 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 2 Sep 2024 16:42:25 +0800 Subject: [PATCH 24/60] chore: bump version 0.6.8 (#6164) * chore: bump version 0.6.8 * chore: update translations --- CHANGELOG.md | 1 + frontend/resources/translations/en.json | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d736863b707..f86f554620dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Bug Fixes - Removed Wayland header from AppImage build - Fixed the issue where pasting web image on mobile failed. +- Fixed the issue where the name of newly created card in board view didn't show up. ## Version 0.6.7 - 13/08/2024 ### New Features diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 1a3914fb97f14..6b0bc6f0d872b 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -288,10 +288,10 @@ "minutesAgo": "{count} minutes ago", "lastViewed": "Last viewed", "favoriteAt": "Favorited", - "emptyRecent": "No Recent Documents", - "emptyRecentDescription": "As you view documents, they will appear here for easy retrieval", - "emptyFavorite": "No Favorite Documents", - "emptyFavoriteDescription": "Start exploring and mark documents as favorites. They’ll be listed here for quick access!", + "emptyRecent": "No Recent Pages", + "emptyRecentDescription": "As you view pages, they will appear here for easy retrieval.", + "emptyFavorite": "No Favorite Pages", + "emptyFavoriteDescription": "Mark pages as favorites—they'll be listed here for quick access!", "removePageFromRecent": "Remove this page from the Recent?", "removeSuccess": "Removed successfully", "favoriteSpace": "Favorites", From 4b0368d5523e7f6bb511ae53ad9d92a46af61ebd Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:50:28 +0800 Subject: [PATCH 25/60] fix: display duplicate entry (#5853) * fix: some issue * fix: add colors * fix: do not allow people who are not appflowy --- .../src/components/as-template/AsTemplate.tsx | 14 +-- .../as-template/creator/CreatorAvatar.tsx | 115 +++++++++++++++--- .../related-template/TemplateItem.tsx | 22 ++-- .../components/calendar/event/Event.tsx | 6 +- .../publish/header/PublishViewHeader.tsx | 23 ++-- .../publish/header/duplicate/Duplicate.tsx | 20 ++- frontend/appflowy_web_app/src/utils/color.ts | 26 ++-- frontend/resources/translations/en.json | 2 +- 8 files changed, 160 insertions(+), 68 deletions(-) diff --git a/frontend/appflowy_web_app/src/components/as-template/AsTemplate.tsx b/frontend/appflowy_web_app/src/components/as-template/AsTemplate.tsx index 16106d24e24fc..32cd5664e3e02 100644 --- a/frontend/appflowy_web_app/src/components/as-template/AsTemplate.tsx +++ b/frontend/appflowy_web_app/src/components/as-template/AsTemplate.tsx @@ -59,17 +59,6 @@ function AsTemplate ({ await loadTemplate(); } - notify.info({ - type: 'success', - title: t('template.uploadSuccess'), - message: t('template.uploadSuccessDescription'), - okText: t('template.viewTemplate'), - onOk: () => { - const url = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io'; - - window.open(`${url}/templates/${selectedCategoryIds[0]}/${viewId}`, '_blank'); - }, - }); handleBack(); } catch (error) { // eslint-disable-next-line @@ -77,8 +66,7 @@ function AsTemplate ({ notify.error(error.toString()); } - }, [service, selectedCreatorId, selectedCategoryIds, viewId, isNewTemplate, isFeatured, viewUrl, template, t, handleBack, loadTemplate]); - + }, [service, selectedCreatorId, selectedCategoryIds, isNewTemplate, isFeatured, viewId, viewUrl, template, loadTemplate, handleBack]); const submitRef = React.useRef(null); useEffect(() => { diff --git a/frontend/appflowy_web_app/src/components/as-template/creator/CreatorAvatar.tsx b/frontend/appflowy_web_app/src/components/as-template/creator/CreatorAvatar.tsx index c68db0f58b0fd..769c7f526d159 100644 --- a/frontend/appflowy_web_app/src/components/as-template/creator/CreatorAvatar.tsx +++ b/frontend/appflowy_web_app/src/components/as-template/creator/CreatorAvatar.tsx @@ -8,7 +8,58 @@ import { ReactComponent as CloudUploadIcon } from '@/assets/cloud_add.svg'; import { useTranslation } from 'react-i18next'; -function CreatorAvatar ({ src, name, enableUpload, onChange, size }: { +const colorArray = [ + '#5287D8', + '#6E9DE3', + '#8BB3ED', + '#A7C9F7', + '#979EB6', + '#A2A8BF', + '#ACB2C8', + '#C1C7DA', + '#E8AF53', + '#E6C25A', + '#E6D26F', + '#E6E288', + '#589599', + '#68AD8E', + '#79C47F', + '#8CDB6A', + '#AA94DC', + '#C49EEB', + '#BAACEE', + '#D5C4FB', + '#F597D2', + '#FCB2E3', + '#FDC5E8', + '#F8D2E1', + '#D1D269', + '#C7C98D', + '#CED09B', + '#DAD9B6', + '#DDD2C6', + '#DDD6C7', + '#EADED3', + '#FED5C4', + '#72A7D8', + '#8FCAE3', + '#64B3DA', + '#52B2D4', + '#90A4FF', + '#A8BEF4', + '#AEBDFF', + '#C2CDFF', + '#86C1B7', + '#A6D8D0', + '#A7D7A8', + '#C8E4C9', + '#FF9494', + '#FFBDBD', + '#DCA8A8', + '#E3C4C4', +]; + +function CreatorAvatar({ src, name, enableUpload, onChange, size }: { src: string; name: string; enableUpload?: boolean; @@ -20,7 +71,7 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: { const [tab, setTab] = React.useState(0); const avatarProps = useMemo(() => { - return stringAvatar(name || ''); + return stringAvatar(name || '', colorArray); }, [name]); const [openModal, setOpenModal] = React.useState(false); @@ -46,15 +97,21 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: { e.stopPropagation(); }} > - {enableUpload && showUpload && ( - +