From 5a837a94826079a092201bebce1d5fa53ad9612c Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:27:57 +0800 Subject: [PATCH] fix: number and select filter logic (#4828) * fix: number and select option filter bugs * chore: rename filter condition enum and variants --- frontend/.vscode/tasks.json | 2 +- .../database/database_filter_test.dart | 4 +- .../database/domain/filter_service.dart | 2 +- .../filter/filter_create_bloc.dart | 8 +- .../filter/select_option_filter_bloc.dart | 4 +- .../select_option_filter_list_bloc.dart | 12 +- .../select_option/condition_list.dart | 111 +- .../choicechip/select_option/option_list.dart | 14 +- .../select_option/select_option.dart | 5 +- .../select_option/select_option_loader.dart | 32 +- .../widgets/filter/choicechip/text.dart | 20 +- .../grid_test/filter/filter_menu_test.dart | 4 +- .../filter/filter_rows_by_text_test.dart | 8 +- .../database/filter/filter_data.ts | 11 +- .../database/filter/filter_types.ts | 4 +- .../filter/FilterConditionSelect.tsx | 57 +- .../filter/select_filter/SelectFilter.tsx | 6 +- .../select_filter/SelectFilterValue.tsx | 10 +- .../filter/text_filter/TextFilterValue.tsx | 12 +- frontend/resources/translations/am-ET.json | 6 +- frontend/resources/translations/ar-SA.json | 8 +- frontend/resources/translations/ca-ES.json | 8 +- frontend/resources/translations/ckb-KU.json | 8 +- frontend/resources/translations/de-DE.json | 8 +- frontend/resources/translations/en.json | 6 +- frontend/resources/translations/es-VE.json | 8 +- frontend/resources/translations/eu-ES.json | 8 +- frontend/resources/translations/fa.json | 8 +- frontend/resources/translations/fr-CA.json | 6 +- frontend/resources/translations/fr-FR.json | 6 +- frontend/resources/translations/hin.json | 1452 ++++++++--------- frontend/resources/translations/hu-HU.json | 8 +- frontend/resources/translations/id-ID.json | 8 +- frontend/resources/translations/it-IT.json | 6 +- frontend/resources/translations/ja-JP.json | 8 +- frontend/resources/translations/ko-KR.json | 8 +- frontend/resources/translations/pl-PL.json | 8 +- frontend/resources/translations/pt-BR.json | 8 +- frontend/resources/translations/pt-PT.json | 8 +- frontend/resources/translations/ru-RU.json | 6 +- frontend/resources/translations/sv-SE.json | 8 +- frontend/resources/translations/th-TH.json | 6 +- frontend/resources/translations/tr-TR.json | 8 +- frontend/resources/translations/uk-UA.json | 8 +- frontend/resources/translations/ur.json | 2 +- frontend/resources/translations/vi-VN.json | 8 +- frontend/resources/translations/zh-CN.json | 6 +- frontend/resources/translations/zh-TW.json | 8 +- .../filter_entities/select_option_filter.rs | 33 +- .../entities/filter_entities/text_filter.rs | 30 +- .../number_type_option/number_filter.rs | 23 +- .../multi_select_type_option.rs | 2 +- .../selection_type_option/select_filter.rs | 438 ++--- .../single_select_type_option.rs | 2 +- .../text_type_option/text_filter.rs | 20 +- .../filter_test/select_option_filter_test.rs | 60 +- .../database/filter_test/text_filter_test.rs | 16 +- 57 files changed, 1338 insertions(+), 1266 deletions(-) diff --git a/frontend/.vscode/tasks.json b/frontend/.vscode/tasks.json index 4253215e27..d940eef0a8 100644 --- a/frontend/.vscode/tasks.json +++ b/frontend/.vscode/tasks.json @@ -257,7 +257,7 @@ "label": "AF: Tauri UI Dev", "type": "shell", "isBackground": true, - "command": "pnpm run sync:i18n && pnpm run dev", + "command": "pnpm sync:i18n && pnpm run dev", "options": { "cwd": "${workspaceFolder}/appflowy_tauri" } diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_filter_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_filter_test.dart index 4dfc64ff62..b6db3e1a62 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_filter_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_filter_test.dart @@ -103,8 +103,8 @@ void main() { // select the option 's4' await tester.tapOptionFilterWithName('s4'); - // The row with 's4' or 's5' should be shown. - await tester.assertNumberOfRowsInGridPage(2); + // The row with 's4' should be shown. + await tester.assertNumberOfRowsInGridPage(1); await tester.pumpAndSettle(); }); diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart index 4b5277d3eb..179ceecfba 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart @@ -171,7 +171,7 @@ class FilterBackendService { Future> insertSelectOptionFilter({ required String fieldId, required FieldType fieldType, - required SelectOptionConditionPB condition, + required SelectOptionFilterConditionPB condition, String? filterId, List optionIds = const [], }) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/filter_create_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/filter_create_bloc.dart index 12dd22f266..d8ea5906a8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/filter_create_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/filter_create_bloc.dart @@ -114,7 +114,7 @@ class GridCreateFilterBloc case FieldType.MultiSelect: return _filterBackendSvc.insertSelectOptionFilter( fieldId: fieldId, - condition: SelectOptionConditionPB.OptionIs, + condition: SelectOptionFilterConditionPB.OptionContains, fieldType: FieldType.MultiSelect, ); case FieldType.Checklist: @@ -130,19 +130,19 @@ class GridCreateFilterBloc case FieldType.RichText: return _filterBackendSvc.insertTextFilter( fieldId: fieldId, - condition: TextFilterConditionPB.Contains, + condition: TextFilterConditionPB.TextContains, content: '', ); case FieldType.SingleSelect: return _filterBackendSvc.insertSelectOptionFilter( fieldId: fieldId, - condition: SelectOptionConditionPB.OptionIs, + condition: SelectOptionFilterConditionPB.OptionIs, fieldType: FieldType.SingleSelect, ); case FieldType.URL: return _filterBackendSvc.insertURLFilter( fieldId: fieldId, - condition: TextFilterConditionPB.Contains, + condition: TextFilterConditionPB.TextContains, ); default: throw UnimplementedError(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart index 9a7ab131b9..3f44cb6d36 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart @@ -38,7 +38,7 @@ class SelectOptionFilterEditorBloc _startListening(); _loadOptions(); }, - updateCondition: (SelectOptionConditionPB condition) { + updateCondition: (SelectOptionFilterConditionPB condition) { _filterBackendSvc.insertSelectOptionFilter( filterId: filterInfo.filter.id, fieldId: filterInfo.fieldInfo.id, @@ -117,7 +117,7 @@ class SelectOptionFilterEditorEvent with _$SelectOptionFilterEditorEvent { FilterPB filter, ) = _DidReceiveFilter; const factory SelectOptionFilterEditorEvent.updateCondition( - SelectOptionConditionPB condition, + SelectOptionFilterConditionPB condition, ) = _UpdateCondition; const factory SelectOptionFilterEditorEvent.updateContent( List optionIds, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart index 6bcc5b8806..278f2a424f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -24,9 +24,12 @@ class SelectOptionFilterListBloc _startListening(); _loadOptions(); }, - selectOption: (option) { - final selectedOptionIds = Set.from(state.selectedOptionIds); - selectedOptionIds.add(option.id); + selectOption: (option, condition) { + final selectedOptionIds = delegate.selectOption( + state.selectedOptionIds, + option.id, + condition, + ); _updateSelectOptions( selectedOptionIds: selectedOptionIds, @@ -116,6 +119,7 @@ class SelectOptionFilterListEvent with _$SelectOptionFilterListEvent { const factory SelectOptionFilterListEvent.initial() = _Initial; const factory SelectOptionFilterListEvent.selectOption( SelectOptionPB option, + SelectOptionFilterConditionPB condition, ) = _SelectOption; const factory SelectOptionFilterListEvent.unselectOption( SelectOptionPB option, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart index 74b1dfdced..bb0a85ac17 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart @@ -1,15 +1,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/condition_button.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; - -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/select_option_filter.pb.dart'; -import 'package:flutter/material.dart'; - -import '../../condition_button.dart'; -import '../../filter_info.dart'; +import 'package:flutter/widgets.dart'; class SelectOptionFilterConditionList extends StatelessWidget { const SelectOptionFilterConditionList({ @@ -21,7 +18,7 @@ class SelectOptionFilterConditionList extends StatelessWidget { final FilterInfo filterInfo; final PopoverMutex popoverMutex; - final Function(SelectOptionConditionPB) onCondition; + final Function(SelectOptionFilterConditionPB) onCondition; @override Widget build(BuildContext context) { @@ -30,18 +27,17 @@ class SelectOptionFilterConditionList extends StatelessWidget { asBarrier: true, mutex: popoverMutex, direction: PopoverDirection.bottomWithCenterAligned, - actions: SelectOptionConditionPB.values + actions: _conditionsForFieldType(filterInfo.fieldInfo.fieldType) .map( (action) => ConditionWrapper( action, selectOptionFilter.condition == action, - filterInfo.fieldInfo.fieldType, ), ) .toList(), buildChild: (controller) { return ConditionButton( - conditionName: filterName(selectOptionFilter), + conditionName: selectOptionFilter.condition.i18n, onTap: () => controller.show(), ); }, @@ -52,69 +48,62 @@ class SelectOptionFilterConditionList extends StatelessWidget { ); } - String filterName(SelectOptionFilterPB filter) { - if (filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) { - return filter.condition.singleSelectFilterName; - } else { - return filter.condition.multiSelectFilterName; - } + List _conditionsForFieldType( + FieldType fieldType, + ) { + // SelectOptionFilterConditionPB.values is not in order + return switch (fieldType) { + FieldType.SingleSelect => [ + SelectOptionFilterConditionPB.OptionIs, + SelectOptionFilterConditionPB.OptionIsNot, + SelectOptionFilterConditionPB.OptionIsEmpty, + SelectOptionFilterConditionPB.OptionIsNotEmpty, + ], + FieldType.MultiSelect => [ + SelectOptionFilterConditionPB.OptionContains, + SelectOptionFilterConditionPB.OptionDoesNotContain, + SelectOptionFilterConditionPB.OptionIs, + SelectOptionFilterConditionPB.OptionIsNot, + SelectOptionFilterConditionPB.OptionIsEmpty, + SelectOptionFilterConditionPB.OptionIsNotEmpty, + ], + _ => [], + }; } } class ConditionWrapper extends ActionCell { - ConditionWrapper(this.inner, this.isSelected, this.fieldType); + ConditionWrapper(this.inner, this.isSelected); - final SelectOptionConditionPB inner; + final SelectOptionFilterConditionPB inner; final bool isSelected; - final FieldType fieldType; @override Widget? rightIcon(Color iconColor) { - if (isSelected) { - return const FlowySvg(FlowySvgs.check_s); - } else { - return null; - } + return isSelected ? const FlowySvg(FlowySvgs.check_s) : null; } @override - String get name { - if (fieldType == FieldType.SingleSelect) { - return inner.singleSelectFilterName; - } else { - return inner.multiSelectFilterName; - } - } + String get name => inner.i18n; } -extension SelectOptionConditionPBExtension on SelectOptionConditionPB { - String get singleSelectFilterName { - switch (this) { - case SelectOptionConditionPB.OptionIs: - return LocaleKeys.grid_singleSelectOptionFilter_is.tr(); - case SelectOptionConditionPB.OptionIsEmpty: - return LocaleKeys.grid_singleSelectOptionFilter_isEmpty.tr(); - case SelectOptionConditionPB.OptionIsNot: - return LocaleKeys.grid_singleSelectOptionFilter_isNot.tr(); - case SelectOptionConditionPB.OptionIsNotEmpty: - return LocaleKeys.grid_singleSelectOptionFilter_isNotEmpty.tr(); - default: - return ""; - } - } - - String get multiSelectFilterName { - switch (this) { - case SelectOptionConditionPB.OptionIs: - return LocaleKeys.grid_multiSelectOptionFilter_contains.tr(); - case SelectOptionConditionPB.OptionIsEmpty: - return LocaleKeys.grid_multiSelectOptionFilter_isEmpty.tr(); - case SelectOptionConditionPB.OptionIsNot: - return LocaleKeys.grid_multiSelectOptionFilter_doesNotContain.tr(); - case SelectOptionConditionPB.OptionIsNotEmpty: - return LocaleKeys.grid_multiSelectOptionFilter_isNotEmpty.tr(); - default: - return ""; - } +extension SelectOptionFilterConditionPBExtension + on SelectOptionFilterConditionPB { + String get i18n { + return switch (this) { + SelectOptionFilterConditionPB.OptionIs => + LocaleKeys.grid_selectOptionFilter_is.tr(), + SelectOptionFilterConditionPB.OptionIsNot => + LocaleKeys.grid_selectOptionFilter_isNot.tr(), + SelectOptionFilterConditionPB.OptionContains => + LocaleKeys.grid_selectOptionFilter_isNot.tr(), + SelectOptionFilterConditionPB.OptionDoesNotContain => + LocaleKeys.grid_selectOptionFilter_isNot.tr(), + SelectOptionFilterConditionPB.OptionIsEmpty => + LocaleKeys.grid_selectOptionFilter_isEmpty.tr(), + SelectOptionFilterConditionPB.OptionIsNotEmpty => + LocaleKeys.grid_selectOptionFilter_isNotEmpty.tr(), + _ => "", + }; } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart index 5d2406094a..67f10d4e3f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database/grid/application/filter/select_option_filter_bloc.dart'; import 'package:appflowy/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart'; @@ -90,9 +91,16 @@ class _SelectOptionFilterCellState extends State { .read() .add(SelectOptionFilterListEvent.unselectOption(widget.option)); } else { - context - .read() - .add(SelectOptionFilterListEvent.selectOption(widget.option)); + context.read().add( + SelectOptionFilterListEvent.selectOption( + widget.option, + context + .read() + .state + .filter + .condition, + ), + ); } }, children: [ diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart index 6c80c9f01a..24d1955a3f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart @@ -101,9 +101,10 @@ class _SelectOptionFilterEditorState extends State { SliverToBoxAdapter(child: _buildFilterPanel(context, state)), ]; - if (state.filter.condition != SelectOptionConditionPB.OptionIsEmpty && + if (state.filter.condition != + SelectOptionFilterConditionPB.OptionIsEmpty && state.filter.condition != - SelectOptionConditionPB.OptionIsNotEmpty) { + SelectOptionFilterConditionPB.OptionIsNotEmpty) { slivers.add(const SliverToBoxAdapter(child: VSpace(4))); slivers.add( SliverToBoxAdapter( diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart index c36ac8c992..502faa9aa0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart @@ -1,9 +1,15 @@ import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; abstract class SelectOptionFilterDelegate { List loadOptions(); + + Set selectOption( + Set currentOptionIds, + String optionId, + SelectOptionFilterConditionPB condition, + ); } class SingleSelectOptionFilterDelegateImpl @@ -17,6 +23,22 @@ class SingleSelectOptionFilterDelegateImpl final parser = SingleSelectTypeOptionDataParser(); return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options; } + + @override + Set selectOption( + Set currentOptionIds, + String optionId, + SelectOptionFilterConditionPB condition, + ) { + final selectOptionIds = Set.from(currentOptionIds); + + if (condition == SelectOptionFilterConditionPB.OptionIsNot || + selectOptionIds.isEmpty) { + selectOptionIds.add(optionId); + } + + return selectOptionIds; + } } class MultiSelectOptionFilterDelegateImpl @@ -30,4 +52,12 @@ class MultiSelectOptionFilterDelegateImpl final parser = MultiSelectTypeOptionDataParser(); return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options; } + + @override + Set selectOption( + Set currentOptionIds, + String optionId, + SelectOptionFilterConditionPB condition, + ) => + Set.from(currentOptionIds)..add(optionId); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart index 9c15af4f93..91c2c063ac 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart @@ -236,17 +236,17 @@ class ConditionWrapper extends ActionCell { extension TextFilterConditionPBExtension on TextFilterConditionPB { String get filterName { switch (this) { - case TextFilterConditionPB.Contains: + case TextFilterConditionPB.TextContains: return LocaleKeys.grid_textFilter_contains.tr(); - case TextFilterConditionPB.DoesNotContain: + case TextFilterConditionPB.TextDoesNotContain: return LocaleKeys.grid_textFilter_doesNotContain.tr(); - case TextFilterConditionPB.EndsWith: + case TextFilterConditionPB.TextEndsWith: return LocaleKeys.grid_textFilter_endsWith.tr(); - case TextFilterConditionPB.Is: + case TextFilterConditionPB.TextIs: return LocaleKeys.grid_textFilter_is.tr(); - case TextFilterConditionPB.IsNot: + case TextFilterConditionPB.TextIsNot: return LocaleKeys.grid_textFilter_isNot.tr(); - case TextFilterConditionPB.StartsWith: + case TextFilterConditionPB.TextStartsWith: return LocaleKeys.grid_textFilter_startWith.tr(); case TextFilterConditionPB.TextIsEmpty: return LocaleKeys.grid_textFilter_isEmpty.tr(); @@ -259,13 +259,13 @@ extension TextFilterConditionPBExtension on TextFilterConditionPB { String get choicechipPrefix { switch (this) { - case TextFilterConditionPB.DoesNotContain: + case TextFilterConditionPB.TextDoesNotContain: return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr(); - case TextFilterConditionPB.EndsWith: + case TextFilterConditionPB.TextEndsWith: return LocaleKeys.grid_textFilter_choicechipPrefix_endWith.tr(); - case TextFilterConditionPB.IsNot: + case TextFilterConditionPB.TextIsNot: return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr(); - case TextFilterConditionPB.StartsWith: + case TextFilterConditionPB.TextStartsWith: return LocaleKeys.grid_textFilter_choicechipPrefix_startWith.tr(); case TextFilterConditionPB.TextIsEmpty: return LocaleKeys.grid_textFilter_choicechipPrefix_isEmpty.tr(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart index 4b35a1dc7a..e76870d33a 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart @@ -55,13 +55,13 @@ void main() { await service.insertTextFilter( fieldId: textField.id, filterId: textFilter.filter.id, - condition: TextFilterConditionPB.Is, + condition: TextFilterConditionPB.TextIs, content: "ABC", ); await gridResponseFuture(); assert( menuBloc.state.filters.first.textFilter()!.condition == - TextFilterConditionPB.Is, + TextFilterConditionPB.TextIs, ); assert(menuBloc.state.filters.first.textFilter()!.content == "ABC"); }); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart index 0e4c79a4e7..0af6b18092 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart @@ -118,7 +118,7 @@ void main() { // create a new filter await service.insertTextFilter( fieldId: textField.id, - condition: TextFilterConditionPB.Is, + condition: TextFilterConditionPB.TextIs, content: "A", ); await gridResponseFuture(); @@ -132,7 +132,7 @@ void main() { await service.insertTextFilter( fieldId: textField.id, filterId: textFilter.filter.id, - condition: TextFilterConditionPB.Is, + condition: TextFilterConditionPB.TextIs, content: "B", ); await gridResponseFuture(); @@ -142,7 +142,7 @@ void main() { await service.insertTextFilter( fieldId: textField.id, filterId: textFilter.filter.id, - condition: TextFilterConditionPB.Is, + condition: TextFilterConditionPB.TextIs, content: "b", ); await gridResponseFuture(); @@ -152,7 +152,7 @@ void main() { await service.insertTextFilter( fieldId: textField.id, filterId: textFilter.filter.id, - condition: TextFilterConditionPB.Is, + condition: TextFilterConditionPB.TextIs, content: "C", ); await gridResponseFuture(); diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts index 5d9c9f9be0..72526b577f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts @@ -3,6 +3,7 @@ import { ChecklistFilterConditionPB, FieldType, NumberFilterConditionPB, + SelectOptionFilterConditionPB, TextFilterConditionPB, } from '@/services/backend'; import { UndeterminedFilter } from '$app/application/database'; @@ -12,7 +13,7 @@ export function getDefaultFilter(fieldType: FieldType): UndeterminedFilter['data case FieldType.RichText: case FieldType.URL: return { - condition: TextFilterConditionPB.Contains, + condition: TextFilterConditionPB.TextContains, content: '', }; case FieldType.Number: @@ -27,6 +28,14 @@ export function getDefaultFilter(fieldType: FieldType): UndeterminedFilter['data return { condition: ChecklistFilterConditionPB.IsIncomplete, }; + case FieldType.SingleSelect: + return { + condition: SelectOptionFilterConditionPB.OptionIs, + }; + case FieldType.MultiSelect: + return { + condition: SelectOptionFilterConditionPB.OptionContains, + }; default: return; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts index 9c83ff01e0..f9f80985e5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts @@ -5,7 +5,7 @@ import { FilterPB, NumberFilterConditionPB, NumberFilterPB, - SelectOptionConditionPB, + SelectOptionFilterConditionPB, SelectOptionFilterPB, TextFilterConditionPB, TextFilterPB, @@ -66,7 +66,7 @@ export interface ChecklistFilterData { } export interface SelectFilterData { - condition?: SelectOptionConditionPB; + condition?: SelectOptionFilterConditionPB; optionIds?: string[]; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx index aaff29287b..8b793942da 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx @@ -6,7 +6,7 @@ import { DateFilterConditionPB, FieldType, NumberFilterConditionPB, - SelectOptionConditionPB, + SelectOptionFilterConditionPB, TextFilterConditionPB, } from '@/services/backend'; @@ -30,27 +30,27 @@ function FilterConditionSelect({ case FieldType.URL: return [ { - value: TextFilterConditionPB.Contains, + value: TextFilterConditionPB.TextContains, text: t('grid.textFilter.contains'), }, { - value: TextFilterConditionPB.DoesNotContain, + value: TextFilterConditionPB.TextDoesNotContain, text: t('grid.textFilter.doesNotContain'), }, { - value: TextFilterConditionPB.StartsWith, + value: TextFilterConditionPB.TextStartsWith, text: t('grid.textFilter.startWith'), }, { - value: TextFilterConditionPB.EndsWith, + value: TextFilterConditionPB.TextEndsWith, text: t('grid.textFilter.endsWith'), }, { - value: TextFilterConditionPB.Is, + value: TextFilterConditionPB.TextIs, text: t('grid.textFilter.is'), }, { - value: TextFilterConditionPB.IsNot, + value: TextFilterConditionPB.TextIsNot, text: t('grid.textFilter.isNot'), }, { @@ -63,26 +63,51 @@ function FilterConditionSelect({ }, ]; case FieldType.SingleSelect: + return [ + { + value: SelectOptionFilterConditionPB.OptionIs, + text: t('grid.selectOptionFilter.is'), + }, + { + value: SelectOptionFilterConditionPB.OptionIsNot, + text: t('grid.selectOptionFilter.isNot'), + }, + { + value: SelectOptionFilterConditionPB.OptionIsEmpty, + text: t('grid.selectOptionFilter.isEmpty'), + }, + { + value: SelectOptionFilterConditionPB.OptionIsNotEmpty, + text: t('grid.selectOptionFilter.isNotEmpty'), + }, + ]; case FieldType.MultiSelect: return [ { - value: SelectOptionConditionPB.OptionIs, - text: t('grid.singleSelectOptionFilter.is'), + value: SelectOptionFilterConditionPB.OptionIs, + text: t('grid.selectOptionFilter.is'), }, { - value: SelectOptionConditionPB.OptionIsNot, - text: t('grid.singleSelectOptionFilter.isNot'), + value: SelectOptionFilterConditionPB.OptionIsNot, + text: t('grid.selectOptionFilter.isNot'), }, { - value: SelectOptionConditionPB.OptionIsEmpty, - text: t('grid.singleSelectOptionFilter.isEmpty'), + value: SelectOptionFilterConditionPB.OptionContains, + text: t('grid.selectOptionFilter.contains'), }, { - value: SelectOptionConditionPB.OptionIsNotEmpty, - text: t('grid.singleSelectOptionFilter.isNotEmpty'), + value: SelectOptionFilterConditionPB.OptionDoesNotContain, + text: t('grid.selectOptionFilter.doesNotContain'), + }, + { + value: SelectOptionFilterConditionPB.OptionIsEmpty, + text: t('grid.selectOptionFilter.isEmpty'), + }, + { + value: SelectOptionFilterConditionPB.OptionIsNotEmpty, + text: t('grid.selectOptionFilter.isNotEmpty'), }, ]; - case FieldType.Number: return [ { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx index 093c1e22b9..bd1d1f239a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx @@ -7,7 +7,7 @@ import { } from '$app/application/database'; import { Tag } from '$app/components/database/components/field_types/select/Tag'; import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import { SelectOptionConditionPB } from '@/services/backend'; +import { SelectOptionFilterConditionPB } from '@/services/backend'; import { useTypeOption } from '$app/components/database'; import KeyboardNavigation, { KeyboardNavigationOption, @@ -42,8 +42,8 @@ function SelectFilter({ onClose, filter, field, onChange }: Props) { const showOptions = options.length > 0 && - condition !== SelectOptionConditionPB.OptionIsEmpty && - condition !== SelectOptionConditionPB.OptionIsNotEmpty; + condition !== SelectOptionFilterConditionPB.OptionIsEmpty && + condition !== SelectOptionFilterConditionPB.OptionIsNotEmpty; const handleChange = ({ condition, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx index 4b6d29d79d..72576deae1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { SelectFilterData, SelectTypeOption } from '$app/application/database'; import { useStaticTypeOption } from '$app/components/database'; import { useTranslation } from 'react-i18next'; -import { SelectOptionConditionPB } from '@/services/backend'; +import { SelectOptionFilterConditionPB } from '@/services/backend'; function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId: string }) { const typeOption = useStaticTypeOption(fieldId); @@ -19,13 +19,13 @@ function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId: .join(', '); switch (data.condition) { - case SelectOptionConditionPB.OptionIs: + case SelectOptionFilterConditionPB.OptionIs: return `: ${options}`; - case SelectOptionConditionPB.OptionIsNot: + case SelectOptionFilterConditionPB.OptionIsNot: return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${options}`; - case SelectOptionConditionPB.OptionIsEmpty: + case SelectOptionFilterConditionPB.OptionIsEmpty: return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`; - case SelectOptionConditionPB.OptionIsNotEmpty: + case SelectOptionFilterConditionPB.OptionIsNotEmpty: return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`; default: return ''; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx index 9377050bf1..5718a3e2b8 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx @@ -9,15 +9,15 @@ function TextFilterValue({ data }: { data: TextFilterData }) { const value = useMemo(() => { if (!data.content) return ''; switch (data.condition) { - case TextFilterConditionPB.Contains: - case TextFilterConditionPB.Is: + case TextFilterConditionPB.TextContains: + case TextFilterConditionPB.TextIs: return `: ${data.content}`; - case TextFilterConditionPB.DoesNotContain: - case TextFilterConditionPB.IsNot: + case TextFilterConditionPB.TextDoesNotContain: + case TextFilterConditionPB.TextIsNot: return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${data.content}`; - case TextFilterConditionPB.StartsWith: + case TextFilterConditionPB.TextStartsWith: return `: ${t('grid.textFilter.choicechipPrefix.startWith')} ${data.content}`; - case TextFilterConditionPB.EndsWith: + case TextFilterConditionPB.TextEndsWith: return `: ${t('grid.textFilter.choicechipPrefix.endWith')} ${data.content}`; case TextFilterConditionPB.TextIsEmpty: return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`; diff --git a/frontend/resources/translations/am-ET.json b/frontend/resources/translations/am-ET.json index f5f11d7495..b07649f0e4 100644 --- a/frontend/resources/translations/am-ET.json +++ b/frontend/resources/translations/am-ET.json @@ -422,13 +422,9 @@ "isComplete": "አይደለም", "isIncomplted": "ባዶ ነው" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "ነው", "isNot": "አይደለም", - "isEmpty": "ባዶ ነው", - "isNotEmpty": "ባዶ አይደለም" - }, - "multiSelectOptionFilter": { "contains": "ይይዛል", "doesNotContain": "አይይዝም", "isEmpty": "ባዶ ነው", diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index f7d273b97b..dc41c2149a 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -486,13 +486,9 @@ "isComplete": "كاملة", "isIncomplted": "غير مكتمل" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "يكون", "isNot": "ليس", - "isEmpty": "فارغ", - "isNotEmpty": "ليس فارغا" - }, - "multiSelectOptionFilter": { "contains": "يتضمن", "doesNotContain": "لا يحتوي", "isEmpty": "فارغ", @@ -1172,4 +1168,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 6638885e09..26ccc6a5ba 100644 --- a/frontend/resources/translations/ca-ES.json +++ b/frontend/resources/translations/ca-ES.json @@ -459,13 +459,9 @@ "isComplete": "està completa", "isIncomplted": "és incompleta" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "És", "isNot": "No és", - "isEmpty": "Està buit", - "isNotEmpty": "No està buit" - }, - "multiSelectOptionFilter": { "contains": "Conté", "doesNotContain": "No conté", "isEmpty": "Està buit", @@ -811,4 +807,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 f5509b5ea1..1a46660fc7 100644 --- a/frontend/resources/translations/ckb-KU.json +++ b/frontend/resources/translations/ckb-KU.json @@ -356,13 +356,9 @@ "isComplete": "تەواوە", "isIncomplted": "ناتەواوە" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "هەیە", "isNot": "نییە", - "isEmpty": "به‌تاڵه‌", - "isNotEmpty": "بەتاڵ نییە" - }, - "multiSelectOptionFilter": { "contains": "لەخۆ دەگرێت", "doesNotContain": "لەخۆناگرێت", "isEmpty": "به‌تاڵه‌", @@ -673,4 +669,4 @@ "frequentlyUsed": "زۆرجار بەکارت هێناوە" } } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/de-DE.json b/frontend/resources/translations/de-DE.json index d2531e2bd0..3d09ac3fee 100644 --- a/frontend/resources/translations/de-DE.json +++ b/frontend/resources/translations/de-DE.json @@ -503,13 +503,9 @@ "isComplete": "ist komplett", "isIncomplted": "ist unvollständig" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Ist", "isNot": "Ist nicht", - "isEmpty": "Ist leer", - "isNotEmpty": "Ist nicht leer" - }, - "multiSelectOptionFilter": { "contains": "Enthält", "doesNotContain": "Beinhaltet nicht", "isEmpty": "Ist leer", @@ -1202,4 +1198,4 @@ "addField": "Ein Feld hinzufügen", "userIcon": "Nutzerbild" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 16e5825b4b..164c64aa06 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -570,13 +570,9 @@ "isComplete": "is complete", "isIncomplted": "is incomplete" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Is", "isNot": "Is not", - "isEmpty": "Is empty", - "isNotEmpty": "Is not empty" - }, - "multiSelectOptionFilter": { "contains": "Contains", "doesNotContain": "Does not contain", "isEmpty": "Is empty", diff --git a/frontend/resources/translations/es-VE.json b/frontend/resources/translations/es-VE.json index fb88f7c1f7..71810fef4a 100644 --- a/frontend/resources/translations/es-VE.json +++ b/frontend/resources/translations/es-VE.json @@ -500,13 +500,9 @@ "isComplete": "Esta completo", "isIncomplted": "esta incompleto" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Es", "isNot": "No es", - "isEmpty": "Esta vacio", - "isNotEmpty": "No está vacío" - }, - "multiSelectOptionFilter": { "contains": "Contiene", "doesNotContain": "No contiene", "isEmpty": "Esta vacio", @@ -1063,4 +1059,4 @@ "backgroundColorPink": "fondo rosa", "backgroundColorRed": "fondo rojo" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/eu-ES.json b/frontend/resources/translations/eu-ES.json index 9c1dbacd5b..9134524084 100644 --- a/frontend/resources/translations/eu-ES.json +++ b/frontend/resources/translations/eu-ES.json @@ -323,13 +323,9 @@ "isComplete": "osatu da", "isIncomplted": "osatu gabe dago" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "da", "isNot": "Ez da", - "isEmpty": "Hutsa dago", - "isNotEmpty": "Ez dago hutsik" - }, - "multiSelectOptionFilter": { "contains": "Duen", "doesNotContain": "Ez dauka", "isEmpty": "Hutsa dago", @@ -600,4 +596,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 5776dcd885..9abbf686f9 100644 --- a/frontend/resources/translations/fa.json +++ b/frontend/resources/translations/fa.json @@ -356,13 +356,9 @@ "isComplete": "کامل است", "isIncomplted": "کامل نیست" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "است", "isNot": "نیست", - "isEmpty": "خالی است", - "isNotEmpty": "خالی نیست" - }, - "multiSelectOptionFilter": { "contains": "شامل", "doesNotContain": "شامل نیست", "isEmpty": "خالی است", @@ -673,4 +669,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 5cdfc71d16..c5c5b4fa51 100644 --- a/frontend/resources/translations/fr-CA.json +++ b/frontend/resources/translations/fr-CA.json @@ -521,13 +521,9 @@ "isComplete": "fait", "isIncomplted": "pas fait" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Est", "isNot": "N'est pas", - "isEmpty": "Est vide", - "isNotEmpty": "N'est pas vide" - }, - "multiSelectOptionFilter": { "contains": "Contient", "doesNotContain": "Ne contient pas", "isEmpty": "Est vide", diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index 1f7935e228..a3c202c662 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -542,13 +542,9 @@ "isComplete": "fait", "isIncomplted": "pas fait" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Est", "isNot": "N'est pas", - "isEmpty": "Est vide", - "isNotEmpty": "N'est pas vide" - }, - "multiSelectOptionFilter": { "contains": "Contient", "doesNotContain": "Ne contient pas", "isEmpty": "Est vide", diff --git a/frontend/resources/translations/hin.json b/frontend/resources/translations/hin.json index 55cbce7f05..8ce86ed96b 100644 --- a/frontend/resources/translations/hin.json +++ b/frontend/resources/translations/hin.json @@ -1,743 +1,739 @@ { - "appName": "AppFlowy", - "defaultUsername": "मैं", - "welcomeText": " @:appName में आपका स्वागत है", - "githubStarText": "गिटहब पर स्टार करे", - "subscribeNewsletterText": "समाचार पत्रिका के लिए सदस्यता लें", - "letsGoButtonText": "जल्दी शुरू करे", - "title": "शीर्षक", - "youCanAlso": "आप भी कर सकते हैं", - "and": "और", - "blockActions": { - "addBelowTooltip": "नीचे जोड़ने के लिए क्लिक करें", - "addAboveCmd": "Alt+click ", - "addAboveMacCmd": "Option+click", - "addAboveTooltip": "ऊपर जोड़ने के लिए", - "dragTooltip": "ले जाने के लिए ड्रैग करें", - "openMenuTooltip": "मेनू खोलने के लिए क्लिक करें" + "appName": "AppFlowy", + "defaultUsername": "मैं", + "welcomeText": " @:appName में आपका स्वागत है", + "githubStarText": "गिटहब पर स्टार करे", + "subscribeNewsletterText": "समाचार पत्रिका के लिए सदस्यता लें", + "letsGoButtonText": "जल्दी शुरू करे", + "title": "शीर्षक", + "youCanAlso": "आप भी कर सकते हैं", + "and": "और", + "blockActions": { + "addBelowTooltip": "नीचे जोड़ने के लिए क्लिक करें", + "addAboveCmd": "Alt+click ", + "addAboveMacCmd": "Option+click", + "addAboveTooltip": "ऊपर जोड़ने के लिए", + "dragTooltip": "ले जाने के लिए ड्रैग करें", + "openMenuTooltip": "मेनू खोलने के लिए क्लिक करें" + }, + "signUp": { + "buttonText": "साइन अप करें", + "title": "साइन अप करें @:appName", + "getStartedText": "शुरू करे", + "emptyPasswordError": "पासवर्ड खाली नहीं हो सकता", + "repeatPasswordEmptyError": "रिपीट पासवर्ड खाली नहीं हो सकता", + "unmatchedPasswordError": "रिपीट पासवर्ड और पासवर्ड एक नहीं है", + "alreadyHaveAnAccount": "क्या आपके पास पहले से एक खाता मौजूद है?", + "emailHint": "ईमेल", + "passwordHint": "पासवर्ड", + "repeatPasswordHint": "रिपीट पासवर्ड", + "signUpWith": "इसके साथ साइन अप करें:" + }, + "signIn": { + "loginTitle": "लॉग इन करें @:appName", + "loginButtonText": "लॉग इन करें", + "loginStartWithAnonymous": "एक अज्ञात सत्र से प्रारंभ करें", + "continueAnonymousUser": "अज्ञात सत्र जारी रखें", + "buttonText": "साइन इन", + "forgotPassword": "पासवर्ड भूल गए?", + "emailHint": "ईमेल", + "passwordHint": "पासवर्ड", + "dontHaveAnAccount": "कोई खाता नहीं है?", + "repeatPasswordEmptyError": "रिपीट पासवर्ड खाली नहीं हो सकता", + "unmatchedPasswordError": "रिपीट पासवर्ड और पासवर्ड एक नहीं है", + "syncPromptMessage": "डेटा को सिंक करने में कुछ समय लग सकता है. कृपया इस पेज को बंद न करें", + "or": "या", + "LogInWithGoogle": "गूगल से लॉग इन करें", + "LogInWithGithub": "गिटहब से लॉग इन करें", + "LogInWithDiscord": "डिस्कॉर्ड से लॉग इन करें", + "signInWith": "इसके साथ साइन इन करें:" + }, + "workspace": { + "chooseWorkspace": "अपना कार्यक्षेत्र चुनें", + "create": "कार्यक्षेत्र बनाएं", + "reset": "कार्यक्षेत्र रीसेट करें", + "resetWorkspacePrompt": "कार्यक्षेत्र को रीसेट करने से उसमें मौजूद सभी पृष्ठ और डेटा हट जाएंगे। क्या आप वाकई कार्यक्षेत्र को रीसेट करना चाहते हैं? वैकल्पिक रूप से, आप कार्यक्षेत्र को पुनर्स्थापित करने के लिए सहायता टीम से संपर्क कर सकते हैं", + "hint": "कार्यक्षेत्र", + "notFoundError": "कार्यस्थल नहीं मिला" + }, + "shareAction": { + "buttonText": "शेयर", + "workInProgress": "जल्द आ रहा है", + "markdown": "markdown", + "csv": "csv", + "copyLink": "लिंक कॉपी करें" + }, + "moreAction": { + "small": "छोटा", + "medium": "मध्यम", + "large": "बड़ा", + "fontSize": "अक्षर का आकर", + "import": "आयात", + "moreOptions": "अधिक विकल्प" + }, + "importPanel": { + "textAndMarkdown": "Text & Markdown", + "documentFromV010": "Document from v0.1.0", + "databaseFromV010": "Database from v0.1.0", + "csv": "CSV", + "database": "Database" + }, + "disclosureAction": { + "rename": "नाम बदलें", + "delete": "हटाएं", + "duplicate": "डुप्लीकेट", + "unfavorite": "पसंदीदा से हटाएँ", + "favorite": "पसंदीदा में जोड़ें", + "openNewTab": "एक नए टैब में खोलें", + "moveTo": "स्थानांतरित करें", + "addToFavorites": "पसंदीदा में जोड़ें", + "copyLink": "कॉपी लिंक" + }, + "blankPageTitle": "रिक्त पेज", + "newPageText": "नया पेज", + "newDocumentText": "नया दस्तावेज़", + "newGridText": "नया ग्रिड", + "newCalendarText": "नया कैलेंडर", + "newBoardText": "नया बोर्ड", + "trash": { + "text": "कचरा", + "restoreAll": "सभी पुनर्स्थापित करें", + "deleteAll": "सभी हटाएँ", + "pageHeader": { + "fileName": "फ़ाइलनाम", + "lastModified": "अंतिम संशोधित", + "created": "बनाया गया" }, - "signUp": { - "buttonText": "साइन अप करें", - "title": "साइन अप करें @:appName", - "getStartedText": "शुरू करे", - "emptyPasswordError": "पासवर्ड खाली नहीं हो सकता", - "repeatPasswordEmptyError": "रिपीट पासवर्ड खाली नहीं हो सकता", - "unmatchedPasswordError": "रिपीट पासवर्ड और पासवर्ड एक नहीं है", - "alreadyHaveAnAccount": "क्या आपके पास पहले से एक खाता मौजूद है?", - "emailHint": "ईमेल", - "passwordHint": "पासवर्ड", - "repeatPasswordHint": "रिपीट पासवर्ड", - "signUpWith": "इसके साथ साइन अप करें:" + "confirmDeleteAll": { + "title": "क्या आप निश्चित रूप से ट्रैश में मौजूद सभी पेज को हटाना चाहते हैं?", + "caption": "यह कार्रवाई पूर्ववत नहीं की जा सकती।" }, - "signIn": { - "loginTitle": "लॉग इन करें @:appName", - "loginButtonText": "लॉग इन करें", - "loginStartWithAnonymous": "एक अज्ञात सत्र से प्रारंभ करें", - "continueAnonymousUser": "अज्ञात सत्र जारी रखें", - "buttonText": "साइन इन", - "forgotPassword": "पासवर्ड भूल गए?", - "emailHint": "ईमेल", - "passwordHint": "पासवर्ड", - "dontHaveAnAccount": "कोई खाता नहीं है?", - "repeatPasswordEmptyError": "रिपीट पासवर्ड खाली नहीं हो सकता", - "unmatchedPasswordError": "रिपीट पासवर्ड और पासवर्ड एक नहीं है", - "syncPromptMessage": "डेटा को सिंक करने में कुछ समय लग सकता है. कृपया इस पेज को बंद न करें", - "or": "या", - "LogInWithGoogle": "गूगल से लॉग इन करें", - "LogInWithGithub": "गिटहब से लॉग इन करें", - "LogInWithDiscord": "डिस्कॉर्ड से लॉग इन करें", - "signInWith": "इसके साथ साइन इन करें:" + "confirmRestoreAll": { + "title": "क्या आप निश्चित रूप से ट्रैश में सभी पेज को पुनर्स्थापित करना चाहते हैं?", + "caption": "यह कार्रवाई पूर्ववत नहीं की जा सकती।" + } + }, + "deletePagePrompt": { + "text": "यह पेज कूड़ेदान में है", + "restore": "पुनर्स्थापित पेज", + "deletePermanent": "स्थायी रूप से हटाएँ" + }, + "dialogCreatePageNameHint": "पेज का नाम", + "questionBubble": { + "shortcuts": "शॉर्टकट", + "whatsNew": "क्या नया है?", + "help": "सहायता", + "markdown": "markdown", + "debug": { + "name": "डीबग जानकारी", + "success": "डिबग जानकारी क्लिपबोर्ड पर कॉपी की गई!", + "fail": "डिबग जानकारी को क्लिपबोर्ड पर कॉपी करने में असमर्थ" }, - "workspace": { - "chooseWorkspace": "अपना कार्यक्षेत्र चुनें", - "create": "कार्यक्षेत्र बनाएं", - "reset": "कार्यक्षेत्र रीसेट करें", - "resetWorkspacePrompt": "कार्यक्षेत्र को रीसेट करने से उसमें मौजूद सभी पृष्ठ और डेटा हट जाएंगे। क्या आप वाकई कार्यक्षेत्र को रीसेट करना चाहते हैं? वैकल्पिक रूप से, आप कार्यक्षेत्र को पुनर्स्थापित करने के लिए सहायता टीम से संपर्क कर सकते हैं", - "hint": "कार्यक्षेत्र", - "notFoundError": "कार्यस्थल नहीं मिला" + "feedback": "जानकारी देना" + }, + "menuAppHeader": { + "moreButtonToolTip": "निकालें, नाम बदलें, और भी बहुत कुछ...", + "addPageTooltip": "जल्दी से अंदर एक पेज जोड़ें", + "defaultNewPageName": "शीर्षकहीन", + "renameDialog": "नाम बदलें" + }, + "toolbar": { + "undo": "अनडू", + "redo": "रीडू", + "bold": "बोल्ड", + "italic": "इटैलिक", + "underline": "अंडरलाइन", + "strike": "स्ट्राइकथ्रू", + "numList": "क्रमांकित सूची", + "bulletList": "बुलेट सूची", + "checkList": "चेकलिस्ट", + "inlineCode": "इनलाइन कोड", + "quote": "कोट", + "header": "हेडर", + "highlight": "हाइलाइट करें", + "color": "रंग", + "addLink": "लिंक जोड़ें", + "link": "लिंक" + }, + "tooltip": { + "lightMode": "लाइट मोड पर स्विच करें", + "darkMode": "डार्क मोड पर स्विच करें", + "openAsPage": "पेज के रूप में खोलें", + "addNewRow": "एक नई पंक्ति जोड़ें", + "openMenu": "मेनू खोलने के लिए क्लिक करें", + "dragRow": "पंक्ति को पुनः व्यवस्थित करने के लिए देर तक दबाएँ", + "viewDataBase": "डेटाबेस देखें", + "referencePage": "यह {name} रफेरेंसेड है", + "addBlockBelow": "नीचे एक ब्लॉक जोड़ें" + }, + "sideBar": { + "closeSidebar": "साइड बार बंद करें", + "openSidebar": "साइड बार खोलें", + "personal": "व्यक्तिगत", + "favorites": "पसंदीदा", + "clickToHidePersonal": "व्यक्तिगत अनुभाग को छिपाने के लिए क्लिक करें", + "clickToHideFavorites": "पसंदीदा अनुभाग को छिपाने के लिए क्लिक करें", + "addAPage": "एक पेज जोड़ें" + }, + "notifications": { + "export": { + "markdown": "आपका नोट मार्कडाउन के रूप में सफलतापूर्वक निर्यात कर दिया गया है।", + "path": "दस्तावेज़/प्रवाह" + } + }, + "contactsPage": { + "title": "संपर्क", + "whatsHappening": "इस सप्ताह क्या हो रहा है?", + "addContact": "संपर्क जोड़ें", + "editContact": "संपर्क संपादित करें" + }, + "button": { + "ok": "ठीक है", + "cancel": "रद्द करें", + "signIn": "साइन इन करें", + "signOut": "साइन आउट करें", + "complete": "पूर्ण", + "save": "सेव", + "generate": "उत्पन्न करें", + "esc": "एस्केप", + "keep": "रखें", + "tryAgain": "फिर से प्रयास करें", + "discard": "त्यागें", + "replace": "बदलें", + "insertBelow": "नीचे डालें", + "upload": "अपलोड करें", + "edit": "संपादित करें", + "delete": "हटाएं", + "duplicate": "डुप्लिकेट", + "done": "किया", + "putback": "पुन्हा डालिए" + }, + "label": { + "welcome": "आपका स्वागत है", + "firstName": "पहला नाम", + "middleName": "मध्य नाम", + "lastName": "अंतिम नाम", + "stepX": "स्टेप {X}" + }, + "oAuth": { + "err": { + "failedTitle": "आपके खाते से जुड़ने में असमर्थ।", + "failedMsg": "कृपया सुनिश्चित करें कि आपने अपने ब्राउज़र में साइन-इन प्रक्रिया पूरी कर ली है।" }, - "shareAction": { - "buttonText": "शेयर", - "workInProgress": "जल्द आ रहा है", - "markdown": "markdown", - "csv": "csv", - "copyLink": "लिंक कॉपी करें" + "google": { + "title": "Google साइन-इन", + "instruction1": "अपने Google संपर्कों को आयात करने के लिए, आपको अपने वेब ब्राउज़र का उपयोग करके इस एप्लिकेशन को अधिकृत करना होगा।", + "instruction2": "आइकन पर क्लिक करके या टेक्स्ट का चयन करके इस कोड को अपने क्लिपबोर्ड पर कॉपी करें:", + "instruction3": "अपने वेब ब्राउज़र में निम्नलिखित लिंक पर जाएँ, और उपरोक्त कोड दर्ज करें", + "instruction4": "साइनअप पूरा होने पर नीचे दिया गया बटन दबाएँ:" + } + }, + "settings": { + "title": "सेटिंग्स", + "menu": { + "appearance": "दृश्य", + "language": "भाषा", + "user": "उपयोगकर्ता", + "files": "फ़ाइलें", + "open": "सेटिंग्स खोलें", + "logout": "लॉगआउट", + "logoutPrompt": "क्या आप निश्चित रूप से लॉगआउट करना चाहते हैं?", + "selfEncryptionLogoutPrompt": "क्या आप वाकई लॉग आउट करना चाहते हैं? कृपया सुनिश्चित करें कि आपने एन्क्रिप्शन रहस्य की कॉपी बना ली है", + "syncSetting": "सिंक सेटिंग", + "enableSync": "सिंक इनेबल करें", + "enableEncrypt": "डेटा एन्क्रिप्ट करें", + "enableEncryptPrompt": "इस रहस्य के साथ अपने डेटा को सुरक्षित करने के लिए एन्क्रिप्शन सक्रिय करें। इसे सुरक्षित रूप से संग्रहीत करें; एक बार सक्षम होने के बाद, इसे बंद नहीं किया जा सकता है। यदि खो जाता है, तो आपका डेटा पुनर्प्राप्त नहीं किया जा सकता है। कॉपी करने के लिए क्लिक करें", + "inputEncryptPrompt": "कृपया अपना एन्क्रिप्शन रहस्य दर्ज करें", + "clickToCopySecret": "गुप्त कॉपी बनाने के लिए क्लिक करें", + "inputTextFieldHint": "आपका रहस्य", + "historicalUserList": "उपयोगकर्ता लॉगिन इतिहास", + "historicalUserListTooltip": "यह सूची आपके अज्ञात खातों को प्रदर्शित करती है। आप किसी खाते का विवरण देखने के लिए उस पर क्लिक कर सकते हैं। 'आरंभ करें' बटन पर क्लिक करके अज्ञात खाते बनाए जाते हैं", + "openHistoricalUser": "अज्ञात खाता खोलने के लिए क्लिक करें" }, - "moreAction": { - "small": "छोटा", - "medium": "मध्यम", - "large": "बड़ा", - "fontSize": "अक्षर का आकर", - "import": "आयात", - "moreOptions": "अधिक विकल्प" - }, - "importPanel": { - "textAndMarkdown": "Text & Markdown", - "documentFromV010": "Document from v0.1.0", - "databaseFromV010": "Database from v0.1.0", - "csv": "CSV", - "database": "Database" - }, - "disclosureAction": { - "rename": "नाम बदलें", - "delete": "हटाएं", - "duplicate": "डुप्लीकेट", - "unfavorite": "पसंदीदा से हटाएँ", - "favorite": "पसंदीदा में जोड़ें", - "openNewTab": "एक नए टैब में खोलें", - "moveTo": "स्थानांतरित करें", - "addToFavorites": "पसंदीदा में जोड़ें", - "copyLink": "कॉपी लिंक" - }, - "blankPageTitle": "रिक्त पेज", - "newPageText": "नया पेज", - "newDocumentText": "नया दस्तावेज़", - "newGridText": "नया ग्रिड", - "newCalendarText": "नया कैलेंडर", - "newBoardText": "नया बोर्ड", - "trash": { - "text": "कचरा", - "restoreAll": "सभी पुनर्स्थापित करें", - "deleteAll": "सभी हटाएँ", - "pageHeader": { - "fileName": "फ़ाइलनाम", - "lastModified": "अंतिम संशोधित", - "created": "बनाया गया" + "appearance": { + "resetSetting": "इस सेटिंग को रीसेट करें", + "fontFamily": { + "label": "फ़ॉन्ट फॅमिली", + "search": "खोजें" }, - "confirmDeleteAll": { - "title": "क्या आप निश्चित रूप से ट्रैश में मौजूद सभी पेज को हटाना चाहते हैं?", - "caption": "यह कार्रवाई पूर्ववत नहीं की जा सकती।" + "themeMode": { + "label": "थीम मोड", + "light": "लाइट मोड", + "dark": "डार्क मोड", + "system": "सिस्टम के अनुसार अनुकूलित करें" }, - "confirmRestoreAll": { - "title": "क्या आप निश्चित रूप से ट्रैश में सभी पेज को पुनर्स्थापित करना चाहते हैं?", - "caption": "यह कार्रवाई पूर्ववत नहीं की जा सकती।" - } - }, - "deletePagePrompt": { - "text": "यह पेज कूड़ेदान में है", - "restore": "पुनर्स्थापित पेज", - "deletePermanent": "स्थायी रूप से हटाएँ" - }, - "dialogCreatePageNameHint": "पेज का नाम", - "questionBubble": { - "shortcuts": "शॉर्टकट", - "whatsNew": "क्या नया है?", - "help": "सहायता", - "markdown": "markdown", - "debug": { - "name": "डीबग जानकारी", - "success": "डिबग जानकारी क्लिपबोर्ड पर कॉपी की गई!", - "fail": "डिबग जानकारी को क्लिपबोर्ड पर कॉपी करने में असमर्थ" + "layoutDirection": { + "label": "लेआउट दिशा", + "hint": "अपनी स्क्रीन पर सामग्री के प्रवाह को बाएँ से दाएँ या दाएँ से बाएँ नियंत्रित करें।", + "ltr": "एलटीआर", + "rtl": "आरटीएल" }, - "feedback": "जानकारी देना" - }, - "menuAppHeader": { - "moreButtonToolTip": "निकालें, नाम बदलें, और भी बहुत कुछ...", - "addPageTooltip": "जल्दी से अंदर एक पेज जोड़ें", - "defaultNewPageName": "शीर्षकहीन", - "renameDialog": "नाम बदलें" - }, - "toolbar": { - "undo": "अनडू", - "redo": "रीडू", - "bold": "बोल्ड", - "italic": "इटैलिक", - "underline": "अंडरलाइन", - "strike": "स्ट्राइकथ्रू", - "numList": "क्रमांकित सूची", - "bulletList": "बुलेट सूची", - "checkList": "चेकलिस्ट", - "inlineCode": "इनलाइन कोड", - "quote": "कोट", - "header": "हेडर", - "highlight": "हाइलाइट करें", - "color": "रंग", - "addLink": "लिंक जोड़ें", - "link": "लिंक" - }, - "tooltip": { - "lightMode": "लाइट मोड पर स्विच करें", - "darkMode": "डार्क मोड पर स्विच करें", - "openAsPage": "पेज के रूप में खोलें", - "addNewRow": "एक नई पंक्ति जोड़ें", - "openMenu": "मेनू खोलने के लिए क्लिक करें", - "dragRow": "पंक्ति को पुनः व्यवस्थित करने के लिए देर तक दबाएँ", - "viewDataBase": "डेटाबेस देखें", - "referencePage": "यह {name} रफेरेंसेड है", - "addBlockBelow": "नीचे एक ब्लॉक जोड़ें" - }, - "sideBar": { - "closeSidebar": "साइड बार बंद करें", - "openSidebar": "साइड बार खोलें", - "personal": "व्यक्तिगत", - "favorites": "पसंदीदा", - "clickToHidePersonal": "व्यक्तिगत अनुभाग को छिपाने के लिए क्लिक करें", - "clickToHideFavorites": "पसंदीदा अनुभाग को छिपाने के लिए क्लिक करें", - "addAPage": "एक पेज जोड़ें" - }, - "notifications": { - "export": { - "markdown": "आपका नोट मार्कडाउन के रूप में सफलतापूर्वक निर्यात कर दिया गया है।", - "path": "दस्तावेज़/प्रवाह" - } - }, - "contactsPage": { - "title": "संपर्क", - "whatsHappening": "इस सप्ताह क्या हो रहा है?", - "addContact": "संपर्क जोड़ें", - "editContact": "संपर्क संपादित करें" - }, - "button": { - "ok": "ठीक है", - "cancel": "रद्द करें", - "signIn": "साइन इन करें", - "signOut": "साइन आउट करें", - "complete": "पूर्ण", - "save": "सेव", - "generate": "उत्पन्न करें", - "esc": "एस्केप", - "keep": "रखें", - "tryAgain": "फिर से प्रयास करें", - "discard": "त्यागें", - "replace": "बदलें", - "insertBelow": "नीचे डालें", - "upload": "अपलोड करें", - "edit": "संपादित करें", - "delete": "हटाएं", - "duplicate": "डुप्लिकेट", - "done": "किया", - "putback": "पुन्हा डालिए" - }, - "label": { - "welcome": "आपका स्वागत है", - "firstName": "पहला नाम", - "middleName": "मध्य नाम", - "lastName": "अंतिम नाम", - "stepX": "स्टेप {X}" - }, - "oAuth": { - "err": { - "failedTitle": "आपके खाते से जुड़ने में असमर्थ।", - "failedMsg": "कृपया सुनिश्चित करें कि आपने अपने ब्राउज़र में साइन-इन प्रक्रिया पूरी कर ली है।" + "textDirection": { + "label": "डिफ़ॉल्ट वाक्य दिशा", + "hint": "निर्दिष्ट करें कि वाक्य को डिफ़ॉल्ट के रूप में बाएँ या दाएँ से प्रारंभ करना चाहिए।", + "ltr": "एलटीआर", + "rtl": "आरटीएल", + "auto": "ऑटो", + "fallback": "लेआउट दिशा के समान" }, - "google": { - "title": "Google साइन-इन", - "instruction1": "अपने Google संपर्कों को आयात करने के लिए, आपको अपने वेब ब्राउज़र का उपयोग करके इस एप्लिकेशन को अधिकृत करना होगा।", - "instruction2": "आइकन पर क्लिक करके या टेक्स्ट का चयन करके इस कोड को अपने क्लिपबोर्ड पर कॉपी करें:", - "instruction3": "अपने वेब ब्राउज़र में निम्नलिखित लिंक पर जाएँ, और उपरोक्त कोड दर्ज करें", - "instruction4": "साइनअप पूरा होने पर नीचे दिया गया बटन दबाएँ:" - } + "themeUpload": { + "button": "अपलोड करें", + "uploadTheme": "थीम अपलोड करें", + "description": "नीचे दिए गए बटन का उपयोग करके अपनी खुद की AppFlowy थीम अपलोड करें।", + "failure": "जो थीम अपलोड किया गया था उसका प्रारूप अमान्य था।", + "loading": "कृपया तब तक प्रतीक्षा करें जब तक हम आपकी थीम को सत्यापित और अपलोड नहीं कर देते...", + "uploadSuccess": "आपका थीम सफलतापूर्वक अपलोड किया गया", + "deletionFailure": "थीम को हटाने में विफल। इसे मैन्युअल रूप से हटाने का प्रयास करें।", + "filePickerDialogTitle": "एक .flowy_plugin फ़ाइल चुनें", + "urlUploadFailure": "URL खोलने में विफल: {}" + }, + "theme": "थीम", + "builtInsLabel": "डिफ़ॉल्ट थीम", + "pluginsLabel": "प्लगइन्स", + "showNamingDialogWhenCreatingPage": "पेज बनाते समय उसका नाम लेने के लिए डायलॉग देखे" + }, + "files": { + "copy": "कॉपी करें", + "defaultLocation": "फ़ाइलें और डेटा संग्रहण स्थान पढ़ें", + "exportData": "अपना डेटा निर्यात करें", + "doubleTapToCopy": "पथ को कॉपी करने के लिए दो बार टैप करें", + "restoreLocation": "AppFlowy डिफ़ॉल्ट पथ पर रीस्टार्ट करें", + "customizeLocation": "कोई अन्य फ़ोल्डर खोलें", + "restartApp": "परिवर्तनों को प्रभावी बनाने के लिए कृपया ऐप को रीस्टार्ट करें।", + "exportDatabase": "डेटाबेस निर्यात करें", + "selectFiles": "उन फ़ाइलों का चयन करें जिन्हें निर्यात करने की आवश्यकता है", + "selectAll": "सभी का चयन करें", + "deselectAll": "सभी को अचयनित करें", + "createNewFolder": "एक नया फ़ोल्डर बनाएँ", + "createNewFolderDesc": "हमें बताएं कि आप अपना डेटा कहां संग्रहीत करना चाहते हैं", + "defineWhereYourDataIsStored": "परिभाषित करें कि आपका डेटा कहाँ संग्रहीत है", + "open": "खोलें", + "openFolder": "मौजूदा फ़ोल्डर खोलें", + "openFolderDesc": "इसे पढ़ें और इसे अपने मौजूदा AppFlowy फ़ोल्डर में लिखें", + "folderHintText": "फ़ोल्डर का नाम", + "location": "एक नया फ़ोल्डर बनाना", + "locationDesc": "अपने AppFlowy डेटा फ़ोल्डर के लिए एक नाम चुनें", + "browser": "ब्राउज़ करें", + "create": "बनाएँ", + "set": "सेट", + "folderPath": "आपके फ़ोल्डर को संग्रहीत करने का पथ", + "locationCannotBeEmpty": "पथ खाली नहीं हो सकता", + "pathCopiedSnackbar": "फ़ाइल संग्रहण पथ क्लिपबोर्ड पर कॉपी किया गया!", + "changeLocationTooltips": "डेटा निर्देशिका बदलें", + "change": "परिवर्तन", + "openLocationTooltips": "अन्य डेटा निर्देशिका खोलें", + "openCurrentDataFolder": "वर्तमान डेटा निर्देशिका खोलें", + "recoverLocationTooltips": "AppFlowy की डिफ़ॉल्ट डेटा निर्देशिका पर रीसेट करें", + "exportFileSuccess": "फ़ाइल सफलतापूर्वक निर्यात हुई", + "exportFileFail": "फ़ाइल निर्यात विफल रहा!", + "export": "निर्यात" + }, + "user": { + "name": "नाम", + "email": "ईमेल", + "tooltipSelectIcon": "आइकन चुनें", + "selectAnIcon": "एक आइकन चुनें", + "pleaseInputYourOpenAIKey": "कृपया अपनी OpenAI key इनपुट करें", + "clickToLogout": "वर्तमान उपयोगकर्ता को लॉगआउट करने के लिए क्लिक करें" + }, + "shortcuts": { + "shortcutsLabel": "शॉर्टकट", + "command": "कमांड", + "keyBinding": "कीबाइंडिंग", + "addNewCommand": "नया कमांड जोड़ें", + "updateShortcutStep": "इच्छित key संयोजन दबाएँ और ENTER दबाएँ", + "shortcutIsAlreadyUsed": "यह शॉर्टकट पहले से ही इसके लिए उपयोग किया जा चुका है: {conflict}", + "resetToDefault": "डिफ़ॉल्ट कीबाइंडिंग पर रीसेट करें", + "couldNotLoadErrorMsg": "शॉर्टकट लोड नहीं हो सका, पुनः प्रयास करें", + "couldNotSaveErrorMsg": "शॉर्टकट सेव नहीं किये जा सके, पुनः प्रयास करें" + } + }, + "grid": { + "deleteView": "क्या आप वाकई इस दृश्य को हटाना चाहते हैं?", + "createView": "नया", + "title": { + "placeholder": "शीर्षकहीन" }, "settings": { - "title": "सेटिंग्स", - "menu": { - "appearance": "दृश्य", - "language": "भाषा", - "user": "उपयोगकर्ता", - "files": "फ़ाइलें", - "open": "सेटिंग्स खोलें", - "logout": "लॉगआउट", - "logoutPrompt": "क्या आप निश्चित रूप से लॉगआउट करना चाहते हैं?", - "selfEncryptionLogoutPrompt": "क्या आप वाकई लॉग आउट करना चाहते हैं? कृपया सुनिश्चित करें कि आपने एन्क्रिप्शन रहस्य की कॉपी बना ली है", - "syncSetting": "सिंक सेटिंग", - "enableSync": "सिंक इनेबल करें", - "enableEncrypt": "डेटा एन्क्रिप्ट करें", - "enableEncryptPrompt": "इस रहस्य के साथ अपने डेटा को सुरक्षित करने के लिए एन्क्रिप्शन सक्रिय करें। इसे सुरक्षित रूप से संग्रहीत करें; एक बार सक्षम होने के बाद, इसे बंद नहीं किया जा सकता है। यदि खो जाता है, तो आपका डेटा पुनर्प्राप्त नहीं किया जा सकता है। कॉपी करने के लिए क्लिक करें", - "inputEncryptPrompt": "कृपया अपना एन्क्रिप्शन रहस्य दर्ज करें", - "clickToCopySecret": "गुप्त कॉपी बनाने के लिए क्लिक करें", - "inputTextFieldHint": "आपका रहस्य", - "historicalUserList": "उपयोगकर्ता लॉगिन इतिहास", - "historicalUserListTooltip": "यह सूची आपके अज्ञात खातों को प्रदर्शित करती है। आप किसी खाते का विवरण देखने के लिए उस पर क्लिक कर सकते हैं। 'आरंभ करें' बटन पर क्लिक करके अज्ञात खाते बनाए जाते हैं", - "openHistoricalUser": "अज्ञात खाता खोलने के लिए क्लिक करें" - }, - "appearance": { - "resetSetting": "इस सेटिंग को रीसेट करें", - "fontFamily": { - "label": "फ़ॉन्ट फॅमिली", - "search": "खोजें" - }, - "themeMode": { - "label": "थीम मोड", - "light": "लाइट मोड", - "dark": "डार्क मोड", - "system": "सिस्टम के अनुसार अनुकूलित करें" - }, - "layoutDirection": { - "label": "लेआउट दिशा", - "hint": "अपनी स्क्रीन पर सामग्री के प्रवाह को बाएँ से दाएँ या दाएँ से बाएँ नियंत्रित करें।", - "ltr": "एलटीआर", - "rtl": "आरटीएल" - }, - "textDirection": { - "label": "डिफ़ॉल्ट वाक्य दिशा", - "hint": "निर्दिष्ट करें कि वाक्य को डिफ़ॉल्ट के रूप में बाएँ या दाएँ से प्रारंभ करना चाहिए।", - "ltr": "एलटीआर", - "rtl": "आरटीएल", - "auto": "ऑटो", - "fallback": "लेआउट दिशा के समान" - }, - "themeUpload": { - "button": "अपलोड करें", - "uploadTheme": "थीम अपलोड करें", - "description": "नीचे दिए गए बटन का उपयोग करके अपनी खुद की AppFlowy थीम अपलोड करें।", - "failure": "जो थीम अपलोड किया गया था उसका प्रारूप अमान्य था।", - "loading": "कृपया तब तक प्रतीक्षा करें जब तक हम आपकी थीम को सत्यापित और अपलोड नहीं कर देते...", - "uploadSuccess": "आपका थीम सफलतापूर्वक अपलोड किया गया", - "deletionFailure": "थीम को हटाने में विफल। इसे मैन्युअल रूप से हटाने का प्रयास करें।", - "filePickerDialogTitle": "एक .flowy_plugin फ़ाइल चुनें", - "urlUploadFailure": "URL खोलने में विफल: {}" - }, - "theme": "थीम", - "builtInsLabel": "डिफ़ॉल्ट थीम", - "pluginsLabel": "प्लगइन्स", - "showNamingDialogWhenCreatingPage": "पेज बनाते समय उसका नाम लेने के लिए डायलॉग देखे" - }, - "files": { - "copy": "कॉपी करें", - "defaultLocation": "फ़ाइलें और डेटा संग्रहण स्थान पढ़ें", - "exportData": "अपना डेटा निर्यात करें", - "doubleTapToCopy": "पथ को कॉपी करने के लिए दो बार टैप करें", - "restoreLocation": "AppFlowy डिफ़ॉल्ट पथ पर रीस्टार्ट करें", - "customizeLocation": "कोई अन्य फ़ोल्डर खोलें", - "restartApp": "परिवर्तनों को प्रभावी बनाने के लिए कृपया ऐप को रीस्टार्ट करें।", - "exportDatabase": "डेटाबेस निर्यात करें", - "selectFiles": "उन फ़ाइलों का चयन करें जिन्हें निर्यात करने की आवश्यकता है", - "selectAll": "सभी का चयन करें", - "deselectAll": "सभी को अचयनित करें", - "createNewFolder": "एक नया फ़ोल्डर बनाएँ", - "createNewFolderDesc": "हमें बताएं कि आप अपना डेटा कहां संग्रहीत करना चाहते हैं", - "defineWhereYourDataIsStored": "परिभाषित करें कि आपका डेटा कहाँ संग्रहीत है", - "open": "खोलें", - "openFolder": "मौजूदा फ़ोल्डर खोलें", - "openFolderDesc": "इसे पढ़ें और इसे अपने मौजूदा AppFlowy फ़ोल्डर में लिखें", - "folderHintText": "फ़ोल्डर का नाम", - "location": "एक नया फ़ोल्डर बनाना", - "locationDesc": "अपने AppFlowy डेटा फ़ोल्डर के लिए एक नाम चुनें", - "browser": "ब्राउज़ करें", - "create": "बनाएँ", - "set": "सेट", - "folderPath": "आपके फ़ोल्डर को संग्रहीत करने का पथ", - "locationCannotBeEmpty": "पथ खाली नहीं हो सकता", - "pathCopiedSnackbar": "फ़ाइल संग्रहण पथ क्लिपबोर्ड पर कॉपी किया गया!", - "changeLocationTooltips": "डेटा निर्देशिका बदलें", - "change": "परिवर्तन", - "openLocationTooltips": "अन्य डेटा निर्देशिका खोलें", - "openCurrentDataFolder": "वर्तमान डेटा निर्देशिका खोलें", - "recoverLocationTooltips": "AppFlowy की डिफ़ॉल्ट डेटा निर्देशिका पर रीसेट करें", - "exportFileSuccess": "फ़ाइल सफलतापूर्वक निर्यात हुई", - "exportFileFail": "फ़ाइल निर्यात विफल रहा!", - "export": "निर्यात" - }, - "user": { - "name": "नाम", - "email": "ईमेल", - "tooltipSelectIcon": "आइकन चुनें", - "selectAnIcon": "एक आइकन चुनें", - "pleaseInputYourOpenAIKey": "कृपया अपनी OpenAI key इनपुट करें", - "clickToLogout": "वर्तमान उपयोगकर्ता को लॉगआउट करने के लिए क्लिक करें" - }, - "shortcuts": { - "shortcutsLabel": "शॉर्टकट", - "command": "कमांड", - "keyBinding": "कीबाइंडिंग", - "addNewCommand": "नया कमांड जोड़ें", - "updateShortcutStep": "इच्छित key संयोजन दबाएँ और ENTER दबाएँ", - "shortcutIsAlreadyUsed": "यह शॉर्टकट पहले से ही इसके लिए उपयोग किया जा चुका है: {conflict}", - "resetToDefault": "डिफ़ॉल्ट कीबाइंडिंग पर रीसेट करें", - "couldNotLoadErrorMsg": "शॉर्टकट लोड नहीं हो सका, पुनः प्रयास करें", - "couldNotSaveErrorMsg": "शॉर्टकट सेव नहीं किये जा सके, पुनः प्रयास करें" - } - }, - "grid": { - "deleteView": "क्या आप वाकई इस दृश्य को हटाना चाहते हैं?", - "createView": "नया", - "title": { - "placeholder": "शीर्षकहीन" - }, - "settings": { - "filter": "फ़िल्टर", - "sort": "क्रमबद्ध करें", - "sortBy": "क्रमबद्ध करें", - "properties": "गुण", - "reorderPropertiesTooltip": "गुणों को पुनः व्यवस्थित करने के लिए खींचें", - "group": "समूह", - "addFilter": "फ़िल्टर करें...", - "deleteFilter": "फ़िल्टर हटाएँ", - "filterBy": "फ़िल्टरबाय...", - "typeAValue": "एक वैल्यू टाइप करें...", - "layout": "लेआउट", - "databaseLayout": "लेआउट" - }, - "textFilter": { - "contains": "शामिल है", - "doesNotContain": "इसमें शामिल नहीं है", - "endsWith": "समाप्त होता है", - "startWith": "से प्रारंभ होता है", - "is": "है", - "isNot": "नहीं है", - "isEmpty": "खाली है", - "isNotEmpty": "खाली नहीं है", - "choicechipPrefix": { - "isNot": "नहीं है", - "startWith": "से प्रारंभ होता है", - "endWith": "के साथ समाप्त होता है", - "isEmpty": "खाली है", - "isNotEmpty": "खाली नहीं है" - } - }, - "checkboxFilter": { - "isChecked": "चेक किया गया", - "isUnchecked": "अनचेक किया हुआ", - "choicechipPrefix": { - "is": "है" - } - }, - "checklistFilter": { - "isComplete": "पूर्ण है", - "isIncomplted": "अपूर्ण है" - }, - "singleSelectOptionFilter": { - "is": "है", - "isNot": "नहीं है", - "isEmpty": "खाली है", - "isNotEmpty": "खाली नहीं है" - }, - "multiSelectOptionFilter": { - "contains": "शामिल है", - "doesNotContain": "इसमें शामिल नहीं है", - "isEmpty": "खाली है", - "isNotEmpty": "खाली नहीं है" - }, - "field": { - "hide": "छिपाएँ", - "insertLeft": "बायाँ सम्मिलित करें", - "insertRight": "दाएँ सम्मिलित करें", - "duplicate": "डुप्लिकेट", - "delete": "हटाएं", - "textFieldName": "लेख", - "checkboxFieldName": "चेकबॉक्स", - "dateFieldName": "दिनांक", - "updatedAtFieldName": "अंतिम संशोधित समय", - "createdAtFieldName": "बनाने का समय", - "numberFieldName": "संख्या", - "singleSelectFieldName": "चुनाव", - "multiSelectFieldName": "बहु चुनाव", - "urlFieldName": "URL", - "checklistFieldName": "चेकलिस्ट", - "numberFormat": "संख्या प्रारूप", - "dateFormat": "दिनांक प्रारूप", - "includeTime": "समय शामिल करें", - "isRange": "अंतिम तिथि", - "dateFormatFriendly": "माह दिन, वर्ष", - "dateFormatISO": "वर्ष-महीना-दिन", - "dateFormatLocal": "महीना/दिन/वर्ष", - "dateFormatUS": "वर्ष/महीना/दिन", - "dateFormatDayMonthYear": "दिन/माह/वर्ष", - "timeFormat": "समय प्रारूप", - "invalidTimeFormat": "अमान्य प्रारूप", - "timeFormatTwelveHour": "१२ घंटा", - "timeFormatTwentyFourHour": "२४ घंटे", - "clearDate": "तिथि मिटाए", - "addSelectOption": "एक विकल्प जोड़ें", - "optionTitle": "विकल्प", - "addOption": "विकल्प जोड़ें", - "editProperty": "डेटा का प्रकार संपादित करें", - "newProperty": "नया डेटा का प्रकार", - "deleteFieldPromptMessage": "क्या आप निश्चित हैं? यह डेटा का प्रकार हटा दी जाएगी", - "newColumn": "नया कॉलम" - }, - "sort": { - "ascending": "असेंडिंग", - "descending": "डिसेंडिंग", - "deleteAllSorts": "सभी प्रकार हटाएँ", - "addSort": "सॉर्ट जोड़ें" - }, - "row": { - "duplicate": "डुप्लिकेट", - "delete": "डिलीट", - "titlePlaceholder": "शीर्षकहीन", - "textPlaceholder": "रिक्त", - "copyProperty": "डेटा के प्रकार को क्लिपबोर्ड पर कॉपी किया गया", - "count": "गिनती", - "newRow": "नई पंक्ति", - "action": "कार्रवाई", - "add": "नीचे जोड़ें पर क्लिक करें", - "drag": "स्थानांतरित करने के लिए खींचें" - }, - "selectOption": { - "create": "बनाएँ", - "purpleColor": "बैंगनी", - "pinkColor": "गुलाबी", - "lightPinkColor": "हल्का गुलाबी", - "orangeColor": "नारंगी", - "yellowColor": "पीला", - "limeColor": "नींबू", - "greenColor": "हरा", - "aquaColor": "एक्वा", - "blueColor": "नीला", - "deleteTag": "टैग हटाएँ", - "colorPanelTitle": "रंग", - "panelTitle": "एक विकल्प चुनें या एक बनाएं", - "searchOption": "एक विकल्प खोजें", - "searchOrCreateOption": "कोई विकल्प खोजें या बनाएँ...", - "createNew": "एक नया बनाएँ", - "orSelectOne": "या एक विकल्प चुनें" - }, - "checklist": { - "taskHint": "कार्य विवरण", - "addNew": "एक नया कार्य जोड़ें", - "submitNewTask": "बनाएँ" - }, - "menuName": "ग्रिड", - "referencedGridPrefix": "का दृश्य" - }, - "document": { - "menuName": "दस्तावेज़ ", - "date": { - "timeHintTextInTwelveHour": "01:00 PM", - "timeHintTextInTwentyFourHour": "13:00" - }, - "slashMenu": { - "board": { - "selectABoardToLinkTo": "लिंक करने के लिए एक बोर्ड चुनें", - "createANewBoard": "एक नया बोर्ड बनाएं" - }, - "grid": { - "selectAGridToLinkTo": "लिंक करने के लिए एक ग्रिड चुनें", - "createANewGrid": "एक नया ग्रिड बनाएं" - }, - "calendar": { - "selectACalendarToLinkTo": "लिंक करने के लिए एक कैलेंडर चुनें", - "createANewCalendar": "एक नया कैलेंडर बनाएं" - } - }, - "selectionMenu": { - "outline": "रूपरेखा", - "codeBlock": "कोड ब्लॉक" - }, - "plugins": { - "referencedBoard": "रेफेरेंस बोर्ड", - "referencedGrid": "रेफेरेंस ग्रिड", - "referencedCalendar": "रेफेरेंस कैलेंडर", - "autoGeneratorMenuItemName": "OpenAI लेखक", - "autoGeneratorTitleName": "OpenAI: AI को कुछ भी लिखने के लिए कहें...", - "autoGeneratorLearnMore": "और जानें", - "autoGeneratorGenerate": "उत्पन्न करें", - "autoGeneratorHintText": "OpenAI से पूछें...", - "autoGeneratorCantGetOpenAIKey": "OpenAI key नहीं मिल सकी", - "autoGeneratorRewrite": "पुनः लिखें", - "smartEdit": "AI सहायक", - "openAI": "OpenAI", - "smartEditFixSpelling": "वर्तनी ठीक करें", - "warning": "⚠️ AI प्रतिक्रियाएँ गलत या भ्रामक हो सकती हैं।", - "smartEditSummarize": "सारांश", - "smartEditImproveWriting": "लेख में सुधार करें", - "smartEditMakeLonger": "लंबा बनाएं", - "smartEditCouldNotFetchResult": "OpenAI से परिणाम प्राप्त नहीं किया जा सका", - "smartEditCouldNotFetchKey": "OpenAI key नहीं लायी जा सकी", - "smartEditDisabled": "सेटिंग्स में OpenAI कनेक्ट करें", - "discardResponse": "क्या आप AI प्रतिक्रियाओं को छोड़ना चाहते हैं?", - "createInlineMathEquation": "समीकरण बनाएं", - "toggleList": "सूची टॉगल करें", - "cover": { - "changeCover": "कवर बदलें", - "colors": "रंग", - "images": "छवियां", - "clearAll": "सभी साफ़ करें", - "abstract": "सार", - "addCover": "कवर जोड़ें", - "addLocalImage": "स्थानीय छवि जोड़ें", - "invalidImageUrl": "अमान्य छवि URL", - "failedToAddImageToGallery": "गैलरी में छवि जोड़ने में विफल", - "enterImageUrl": "छवि URL दर्ज करें", - "add": "जोड़ें", - "back": "पीछे", - "saveToGallery": "गैलरी में सेव करे", - "removeIcon": "आइकन हटाएँ", - "pasteImageUrl": "छवि URL चिपकाएँ", - "or": "या", - "pickFromFiles": "फ़ाइलों में से चुनें", - "couldNotFetchImage": "छवि नहीं लाया जा सका", - "imageSavingFailed": "छवि सहेजना विफल", - "addIcon": "आइकन जोड़ें", - "coverRemoveAlert": "हटाने के बाद इसे कवर से हटा दिया जाएगा।", - "alertDialogConfirmation": "क्या आप निश्चित हैं, आप जारी रखना चाहते हैं?" - }, - "mathEquation": { - "addMathEquation": "गणित समीकरण जोड़ें", - "editMathEquation": "गणित समीकरण संपादित करें" - }, - "optionAction": { - "click": "क्लिक करें", - "toOpenMenu": "मेनू खोलने के लिए", - "delete": "हटाएं", - "duplicate": "डुप्लिकेट", - "turnInto": "टर्नइनटू", - "moveUp": "ऊपर बढ़ें", - "moveDown": "नीचे जाएँ", - "color": "रंग", - "align": "संरेखित करें", - "left": "बांया", - "center": "केंद्र", - "right": "सही", - "defaultColor": "डिफ़ॉल्ट" - }, - "image": { - "copiedToPasteBoard": "छवि लिंक को क्लिपबोर्ड पर कॉपी कर दिया गया है" - }, - "outline": { - "addHeadingToCreateOutline": "सामग्री की तालिका बनाने के लिए शीर्षक जोड़ें।" - }, - "table": { - "addAfter": "बाद में जोड़ें", - "addBefore": "पहले जोड़ें", - "delete": "हटाएं", - "clear": "साफ़ करें", - "duplicate": "डुप्लिकेट", - "bgColor": "पृष्ठभूमि रंग" - }, - "contextMenu": { - "copy": "कॉपी करें", - "cut": "कट करे", - "paste": "पेस्ट करें" - } - }, - "textBlock": { - "placeholder": "कमांड के लिए '/' टाइप करें" - }, - "title": { - "placeholder": "शीर्षकहीन" - }, - "imageBlock": { - "placeholder": "छवि जोड़ने के लिए क्लिक करें", - "अपलोड करें": { - "label": "अपलोड करें", - "placeholder": "छवि अपलोड करने के लिए क्लिक करें" - }, - "url": { - "label": "छवि URL ", - "placeholder": "छवि URL दर्ज करें" - }, - "support": "छवि आकार सीमा 5 एमबी है। समर्थित प्रारूप: JPEG, PNG, GIF, SVG", - "error": { - "invalidImage": "अमान्य छवि", - "invalidImageSize": "छवि का आकार 5MB से कम होना चाहिए", - "invalidImageFormat": "छवि प्रारूप समर्थित नहीं है। समर्थित प्रारूप: JPEG, PNG, GIF, SVG", - "invalidImageUrl": "अमान्य छवि URL" - } - }, - "codeBlock": { - "language": { - "label": "भाषा", - "placeholder": "भाषा चुनें" - } - }, - "inlineLink": { - "placeholder": "लिंक चिपकाएँ या टाइप करें", - "openInNewTab": "नए टैब में खोलें", - "copyLink": "लिंक कॉपी करें", - "removeLink": "लिंक हटाएँ", - "url": { - "label": "लिंक URL", - "placeholder": "लिंक URL दर्ज करें" - }, - "title": { - "label": "लिंक शीर्षक", - "placeholder": "लिंक शीर्षक दर्ज करें" - } - }, - "mention": { - "placeholder": "किसी व्यक्ति या पेज या दिनांक का उल्लेख करें...", - "page": { - "label": "पेज से लिंक करें", - "tooltip": "पेज खोलने के लिए क्लिक करें" - } - }, - "toolbar": { - "resetToDefaultFont": "डिफ़ॉल्ट पर रीसेट करें" - } - }, - "board": { - "column": { - "createNewCard": "नया" - }, - "menuName": "बोर्ड", - "referencedBoardPrefix": "का दृश्य" - }, - "calendar": { - "menuName": "कैलेंडर", - "defaultNewCalendarTitle": "शीर्षकहीन", - "newEventButtonTooltip": "एक नया ईवेंट जोड़ें", - "navigation": { - "today": "आज", - "jumpToday": "जम्प टू टुडे", - "previousMonth": "पिछला महीना", - "nextMonth": "अगले महीने" - }, - "settings": { - "showWeekNumbers": "सप्ताह संख्याएँ दिखाएँ", - "showWeekends": "सप्ताहांत दिखाएँ", - "firstDayOfWeek": "सप्ताह प्रारंभ करें", - "layoutDateField": "लेआउट कैलेंडर", - "noDateTitle": "कोई दिनांक नहीं", - "noDateHint": "अनिर्धारित घटनाएँ यहाँ दिखाई देंगी", - "clickToAdd": "कैलेंडर में जोड़ने के लिए क्लिक करें", - "name": "कैलेंडर लेआउट" - }, - "referencedCalendarPrefix": "का दृश्य" - }, - "errorDialog": { - "title": "AppFlowy error", - "howToFixFallback": "असुविधा के लिए हमें खेद है! हमारे GitHub पेज पर एक मुद्दा सबमिट करें जो आपकी error का वर्णन करता है।", - "github": "GitHub पर देखें " - }, - "search": { - "label": "खोजें", - "placeholder": { - "actions": "खोज क्रियाएँ..." - } - }, - "message": { - "copy": { - "success": "कॉपी सफलता पूर्ण हुआ!", - "fail": "कॉपी करने में असमर्थ" - } - }, - "unSupportBlock": "वर्तमान संस्करण इस ब्लॉक का समर्थन नहीं करता है।", - "views": { - "deleteContentTitle": "क्या आप वाकई {pageType} को हटाना चाहते हैं?", - "deleteContentCaption": "यदि आप इस {pageType} को हटाते हैं, तो आप इसे ट्रैश से पुनर्स्थापित कर सकते हैं।" - }, - "colors": { - "custom": "कस्टम", - "default": "डिफ़ॉल्ट", - "red": "लाल", - "orange": "नारंगी", - "yellow": "पीला", - "green": "हरा", - "blue": "नीला", - "purple": "बैंगनी", - "pink": "गुलाबी", - "brown": "भूरा", - "gray": "ग्रे" - }, - "emoji": { "filter": "फ़िल्टर", - "random": "रैंडम", - "selectSkinTone": "त्वचा का रंग चुनें", - "remove": "इमोजी हटाएं", - "categories": { - "smileys": "स्माइलीज़ एंड इमोशन", - "people": "लोग और शरीर", - "animals": "जानवर और प्रकृति", - "food": "खाद्य और पेय", - "activities": "गतिविधियाँ", - "places": "यात्रा एवं स्थान", - "objects": "ऑब्जेक्ट्स", - "symbols": "प्रतीक", - "flags": "झंडे", - "nature": "प्रकृति", - "frequentlyUsed": "अक्सर उपयोग किया जाता है" + "sort": "क्रमबद्ध करें", + "sortBy": "क्रमबद्ध करें", + "properties": "गुण", + "reorderPropertiesTooltip": "गुणों को पुनः व्यवस्थित करने के लिए खींचें", + "group": "समूह", + "addFilter": "फ़िल्टर करें...", + "deleteFilter": "फ़िल्टर हटाएँ", + "filterBy": "फ़िल्टरबाय...", + "typeAValue": "एक वैल्यू टाइप करें...", + "layout": "लेआउट", + "databaseLayout": "लेआउट" + }, + "textFilter": { + "contains": "शामिल है", + "doesNotContain": "इसमें शामिल नहीं है", + "endsWith": "समाप्त होता है", + "startWith": "से प्रारंभ होता है", + "is": "है", + "isNot": "नहीं है", + "isEmpty": "खाली है", + "isNotEmpty": "खाली नहीं है", + "choicechipPrefix": { + "isNot": "नहीं है", + "startWith": "से प्रारंभ होता है", + "endWith": "के साथ समाप्त होता है", + "isEmpty": "खाली है", + "isNotEmpty": "खाली नहीं है" } + }, + "checkboxFilter": { + "isChecked": "चेक किया गया", + "isUnchecked": "अनचेक किया हुआ", + "choicechipPrefix": { + "is": "है" + } + }, + "checklistFilter": { + "isComplete": "पूर्ण है", + "isIncomplted": "अपूर्ण है" + }, + "selectOptionFilter": { + "is": "है", + "isNot": "नहीं है", + "contains": "शामिल है", + "doesNotContain": "इसमें शामिल नहीं है", + "isEmpty": "खाली है", + "isNotEmpty": "खाली नहीं है" + }, + "field": { + "hide": "छिपाएँ", + "insertLeft": "बायाँ सम्मिलित करें", + "insertRight": "दाएँ सम्मिलित करें", + "duplicate": "डुप्लिकेट", + "delete": "हटाएं", + "textFieldName": "लेख", + "checkboxFieldName": "चेकबॉक्स", + "dateFieldName": "दिनांक", + "updatedAtFieldName": "अंतिम संशोधित समय", + "createdAtFieldName": "बनाने का समय", + "numberFieldName": "संख्या", + "singleSelectFieldName": "चुनाव", + "multiSelectFieldName": "बहु चुनाव", + "urlFieldName": "URL", + "checklistFieldName": "चेकलिस्ट", + "numberFormat": "संख्या प्रारूप", + "dateFormat": "दिनांक प्रारूप", + "includeTime": "समय शामिल करें", + "isRange": "अंतिम तिथि", + "dateFormatFriendly": "माह दिन, वर्ष", + "dateFormatISO": "वर्ष-महीना-दिन", + "dateFormatLocal": "महीना/दिन/वर्ष", + "dateFormatUS": "वर्ष/महीना/दिन", + "dateFormatDayMonthYear": "दिन/माह/वर्ष", + "timeFormat": "समय प्रारूप", + "invalidTimeFormat": "अमान्य प्रारूप", + "timeFormatTwelveHour": "१२ घंटा", + "timeFormatTwentyFourHour": "२४ घंटे", + "clearDate": "तिथि मिटाए", + "addSelectOption": "एक विकल्प जोड़ें", + "optionTitle": "विकल्प", + "addOption": "विकल्प जोड़ें", + "editProperty": "डेटा का प्रकार संपादित करें", + "newProperty": "नया डेटा का प्रकार", + "deleteFieldPromptMessage": "क्या आप निश्चित हैं? यह डेटा का प्रकार हटा दी जाएगी", + "newColumn": "नया कॉलम" + }, + "sort": { + "ascending": "असेंडिंग", + "descending": "डिसेंडिंग", + "deleteAllSorts": "सभी प्रकार हटाएँ", + "addSort": "सॉर्ट जोड़ें" + }, + "row": { + "duplicate": "डुप्लिकेट", + "delete": "डिलीट", + "titlePlaceholder": "शीर्षकहीन", + "textPlaceholder": "रिक्त", + "copyProperty": "डेटा के प्रकार को क्लिपबोर्ड पर कॉपी किया गया", + "count": "गिनती", + "newRow": "नई पंक्ति", + "action": "कार्रवाई", + "add": "नीचे जोड़ें पर क्लिक करें", + "drag": "स्थानांतरित करने के लिए खींचें" + }, + "selectOption": { + "create": "बनाएँ", + "purpleColor": "बैंगनी", + "pinkColor": "गुलाबी", + "lightPinkColor": "हल्का गुलाबी", + "orangeColor": "नारंगी", + "yellowColor": "पीला", + "limeColor": "नींबू", + "greenColor": "हरा", + "aquaColor": "एक्वा", + "blueColor": "नीला", + "deleteTag": "टैग हटाएँ", + "colorPanelTitle": "रंग", + "panelTitle": "एक विकल्प चुनें या एक बनाएं", + "searchOption": "एक विकल्प खोजें", + "searchOrCreateOption": "कोई विकल्प खोजें या बनाएँ...", + "createNew": "एक नया बनाएँ", + "orSelectOne": "या एक विकल्प चुनें" + }, + "checklist": { + "taskHint": "कार्य विवरण", + "addNew": "एक नया कार्य जोड़ें", + "submitNewTask": "बनाएँ" + }, + "menuName": "ग्रिड", + "referencedGridPrefix": "का दृश्य" + }, + "document": { + "menuName": "दस्तावेज़ ", + "date": { + "timeHintTextInTwelveHour": "01:00 PM", + "timeHintTextInTwentyFourHour": "13:00" + }, + "slashMenu": { + "board": { + "selectABoardToLinkTo": "लिंक करने के लिए एक बोर्ड चुनें", + "createANewBoard": "एक नया बोर्ड बनाएं" + }, + "grid": { + "selectAGridToLinkTo": "लिंक करने के लिए एक ग्रिड चुनें", + "createANewGrid": "एक नया ग्रिड बनाएं" + }, + "calendar": { + "selectACalendarToLinkTo": "लिंक करने के लिए एक कैलेंडर चुनें", + "createANewCalendar": "एक नया कैलेंडर बनाएं" + } + }, + "selectionMenu": { + "outline": "रूपरेखा", + "codeBlock": "कोड ब्लॉक" + }, + "plugins": { + "referencedBoard": "रेफेरेंस बोर्ड", + "referencedGrid": "रेफेरेंस ग्रिड", + "referencedCalendar": "रेफेरेंस कैलेंडर", + "autoGeneratorMenuItemName": "OpenAI लेखक", + "autoGeneratorTitleName": "OpenAI: AI को कुछ भी लिखने के लिए कहें...", + "autoGeneratorLearnMore": "और जानें", + "autoGeneratorGenerate": "उत्पन्न करें", + "autoGeneratorHintText": "OpenAI से पूछें...", + "autoGeneratorCantGetOpenAIKey": "OpenAI key नहीं मिल सकी", + "autoGeneratorRewrite": "पुनः लिखें", + "smartEdit": "AI सहायक", + "openAI": "OpenAI", + "smartEditFixSpelling": "वर्तनी ठीक करें", + "warning": "⚠️ AI प्रतिक्रियाएँ गलत या भ्रामक हो सकती हैं।", + "smartEditSummarize": "सारांश", + "smartEditImproveWriting": "लेख में सुधार करें", + "smartEditMakeLonger": "लंबा बनाएं", + "smartEditCouldNotFetchResult": "OpenAI से परिणाम प्राप्त नहीं किया जा सका", + "smartEditCouldNotFetchKey": "OpenAI key नहीं लायी जा सकी", + "smartEditDisabled": "सेटिंग्स में OpenAI कनेक्ट करें", + "discardResponse": "क्या आप AI प्रतिक्रियाओं को छोड़ना चाहते हैं?", + "createInlineMathEquation": "समीकरण बनाएं", + "toggleList": "सूची टॉगल करें", + "cover": { + "changeCover": "कवर बदलें", + "colors": "रंग", + "images": "छवियां", + "clearAll": "सभी साफ़ करें", + "abstract": "सार", + "addCover": "कवर जोड़ें", + "addLocalImage": "स्थानीय छवि जोड़ें", + "invalidImageUrl": "अमान्य छवि URL", + "failedToAddImageToGallery": "गैलरी में छवि जोड़ने में विफल", + "enterImageUrl": "छवि URL दर्ज करें", + "add": "जोड़ें", + "back": "पीछे", + "saveToGallery": "गैलरी में सेव करे", + "removeIcon": "आइकन हटाएँ", + "pasteImageUrl": "छवि URL चिपकाएँ", + "or": "या", + "pickFromFiles": "फ़ाइलों में से चुनें", + "couldNotFetchImage": "छवि नहीं लाया जा सका", + "imageSavingFailed": "छवि सहेजना विफल", + "addIcon": "आइकन जोड़ें", + "coverRemoveAlert": "हटाने के बाद इसे कवर से हटा दिया जाएगा।", + "alertDialogConfirmation": "क्या आप निश्चित हैं, आप जारी रखना चाहते हैं?" + }, + "mathEquation": { + "addMathEquation": "गणित समीकरण जोड़ें", + "editMathEquation": "गणित समीकरण संपादित करें" + }, + "optionAction": { + "click": "क्लिक करें", + "toOpenMenu": "मेनू खोलने के लिए", + "delete": "हटाएं", + "duplicate": "डुप्लिकेट", + "turnInto": "टर्नइनटू", + "moveUp": "ऊपर बढ़ें", + "moveDown": "नीचे जाएँ", + "color": "रंग", + "align": "संरेखित करें", + "left": "बांया", + "center": "केंद्र", + "right": "सही", + "defaultColor": "डिफ़ॉल्ट" + }, + "image": { + "copiedToPasteBoard": "छवि लिंक को क्लिपबोर्ड पर कॉपी कर दिया गया है" + }, + "outline": { + "addHeadingToCreateOutline": "सामग्री की तालिका बनाने के लिए शीर्षक जोड़ें।" + }, + "table": { + "addAfter": "बाद में जोड़ें", + "addBefore": "पहले जोड़ें", + "delete": "हटाएं", + "clear": "साफ़ करें", + "duplicate": "डुप्लिकेट", + "bgColor": "पृष्ठभूमि रंग" + }, + "contextMenu": { + "copy": "कॉपी करें", + "cut": "कट करे", + "paste": "पेस्ट करें" + } + }, + "textBlock": { + "placeholder": "कमांड के लिए '/' टाइप करें" + }, + "title": { + "placeholder": "शीर्षकहीन" + }, + "imageBlock": { + "placeholder": "छवि जोड़ने के लिए क्लिक करें", + "अपलोड करें": { + "label": "अपलोड करें", + "placeholder": "छवि अपलोड करने के लिए क्लिक करें" + }, + "url": { + "label": "छवि URL ", + "placeholder": "छवि URL दर्ज करें" + }, + "support": "छवि आकार सीमा 5 एमबी है। समर्थित प्रारूप: JPEG, PNG, GIF, SVG", + "error": { + "invalidImage": "अमान्य छवि", + "invalidImageSize": "छवि का आकार 5MB से कम होना चाहिए", + "invalidImageFormat": "छवि प्रारूप समर्थित नहीं है। समर्थित प्रारूप: JPEG, PNG, GIF, SVG", + "invalidImageUrl": "अमान्य छवि URL" + } + }, + "codeBlock": { + "language": { + "label": "भाषा", + "placeholder": "भाषा चुनें" + } + }, + "inlineLink": { + "placeholder": "लिंक चिपकाएँ या टाइप करें", + "openInNewTab": "नए टैब में खोलें", + "copyLink": "लिंक कॉपी करें", + "removeLink": "लिंक हटाएँ", + "url": { + "label": "लिंक URL", + "placeholder": "लिंक URL दर्ज करें" + }, + "title": { + "label": "लिंक शीर्षक", + "placeholder": "लिंक शीर्षक दर्ज करें" + } + }, + "mention": { + "placeholder": "किसी व्यक्ति या पेज या दिनांक का उल्लेख करें...", + "page": { + "label": "पेज से लिंक करें", + "tooltip": "पेज खोलने के लिए क्लिक करें" + } + }, + "toolbar": { + "resetToDefaultFont": "डिफ़ॉल्ट पर रीसेट करें" } - } \ No newline at end of file + }, + "board": { + "column": { + "createNewCard": "नया" + }, + "menuName": "बोर्ड", + "referencedBoardPrefix": "का दृश्य" + }, + "calendar": { + "menuName": "कैलेंडर", + "defaultNewCalendarTitle": "शीर्षकहीन", + "newEventButtonTooltip": "एक नया ईवेंट जोड़ें", + "navigation": { + "today": "आज", + "jumpToday": "जम्प टू टुडे", + "previousMonth": "पिछला महीना", + "nextMonth": "अगले महीने" + }, + "settings": { + "showWeekNumbers": "सप्ताह संख्याएँ दिखाएँ", + "showWeekends": "सप्ताहांत दिखाएँ", + "firstDayOfWeek": "सप्ताह प्रारंभ करें", + "layoutDateField": "लेआउट कैलेंडर", + "noDateTitle": "कोई दिनांक नहीं", + "noDateHint": "अनिर्धारित घटनाएँ यहाँ दिखाई देंगी", + "clickToAdd": "कैलेंडर में जोड़ने के लिए क्लिक करें", + "name": "कैलेंडर लेआउट" + }, + "referencedCalendarPrefix": "का दृश्य" + }, + "errorDialog": { + "title": "AppFlowy error", + "howToFixFallback": "असुविधा के लिए हमें खेद है! हमारे GitHub पेज पर एक मुद्दा सबमिट करें जो आपकी error का वर्णन करता है।", + "github": "GitHub पर देखें " + }, + "search": { + "label": "खोजें", + "placeholder": { + "actions": "खोज क्रियाएँ..." + } + }, + "message": { + "copy": { + "success": "कॉपी सफलता पूर्ण हुआ!", + "fail": "कॉपी करने में असमर्थ" + } + }, + "unSupportBlock": "वर्तमान संस्करण इस ब्लॉक का समर्थन नहीं करता है।", + "views": { + "deleteContentTitle": "क्या आप वाकई {pageType} को हटाना चाहते हैं?", + "deleteContentCaption": "यदि आप इस {pageType} को हटाते हैं, तो आप इसे ट्रैश से पुनर्स्थापित कर सकते हैं।" + }, + "colors": { + "custom": "कस्टम", + "default": "डिफ़ॉल्ट", + "red": "लाल", + "orange": "नारंगी", + "yellow": "पीला", + "green": "हरा", + "blue": "नीला", + "purple": "बैंगनी", + "pink": "गुलाबी", + "brown": "भूरा", + "gray": "ग्रे" + }, + "emoji": { + "filter": "फ़िल्टर", + "random": "रैंडम", + "selectSkinTone": "त्वचा का रंग चुनें", + "remove": "इमोजी हटाएं", + "categories": { + "smileys": "स्माइलीज़ एंड इमोशन", + "people": "लोग और शरीर", + "animals": "जानवर और प्रकृति", + "food": "खाद्य और पेय", + "activities": "गतिविधियाँ", + "places": "यात्रा एवं स्थान", + "objects": "ऑब्जेक्ट्स", + "symbols": "प्रतीक", + "flags": "झंडे", + "nature": "प्रकृति", + "frequentlyUsed": "अक्सर उपयोग किया जाता है" + } + } +} \ No newline at end of file diff --git a/frontend/resources/translations/hu-HU.json b/frontend/resources/translations/hu-HU.json index 2f3c0cfece..60373c39f3 100644 --- a/frontend/resources/translations/hu-HU.json +++ b/frontend/resources/translations/hu-HU.json @@ -325,13 +325,9 @@ "isComplete": "teljes", "isIncomplted": "hiányos" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Is", "isNot": "Nem", - "isEmpty": "Üres", - "isNotEmpty": "Nem üres" - }, - "multiSelectOptionFilter": { "contains": "Tartalmaz", "doesNotContain": "Nem tartalmaz", "isEmpty": "Üres", @@ -598,4 +594,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 bf37c0c7ac..2bc864d67f 100644 --- a/frontend/resources/translations/id-ID.json +++ b/frontend/resources/translations/id-ID.json @@ -452,13 +452,9 @@ "isComplete": "selesai", "isIncomplted": "tidak lengkap" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Adalah", "isNot": "Tidak", - "isEmpty": "Kosong", - "isNotEmpty": "Tidak kosong" - }, - "multiSelectOptionFilter": { "contains": "Mengandung", "doesNotContain": "Tidak mengandung", "isEmpty": "Kosong", @@ -1021,4 +1017,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 d8ffd8e59e..828bd30e7e 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -516,13 +516,9 @@ "isComplete": "è completo", "isIncomplted": "è incompleto" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "È", "isNot": "Non è", - "isEmpty": "È vuoto", - "isNotEmpty": "Non è vuoto" - }, - "multiSelectOptionFilter": { "contains": "Contiene", "doesNotContain": "Non contiene", "isEmpty": "È vuoto", diff --git a/frontend/resources/translations/ja-JP.json b/frontend/resources/translations/ja-JP.json index 0a8364a2dc..35e348c96f 100644 --- a/frontend/resources/translations/ja-JP.json +++ b/frontend/resources/translations/ja-JP.json @@ -412,13 +412,9 @@ "isComplete": "完了", "isIncomplted": "未完了" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "等しい", "isNot": "等しくない", - "isEmpty": "空である", - "isNotEmpty": "空ではない" - }, - "multiSelectOptionFilter": { "contains": "を含む", "doesNotContain": "を含まない", "isEmpty": "空である", @@ -685,4 +681,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 4570f95d23..0ca45882bf 100644 --- a/frontend/resources/translations/ko-KR.json +++ b/frontend/resources/translations/ko-KR.json @@ -324,13 +324,9 @@ "isComplete": "완료되었습니다", "isIncomplted": "불완전하다" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "~이다", "isNot": "아니다", - "isEmpty": "비었다", - "isNotEmpty": "비어 있지 않음" - }, - "multiSelectOptionFilter": { "contains": "포함", "doesNotContain": "포함되어 있지 않다", "isEmpty": "비었다", @@ -597,4 +593,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 89657c91c8..2d1f66fddd 100644 --- a/frontend/resources/translations/pl-PL.json +++ b/frontend/resources/translations/pl-PL.json @@ -454,13 +454,9 @@ "isComplete": "jest kompletna", "isIncomplted": "jest niekompletna" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Jest", "isNot": "Nie jest", - "isEmpty": "Jest pusty", - "isNotEmpty": "Nie jest pusty" - }, - "multiSelectOptionFilter": { "contains": "Zawiera", "doesNotContain": "Nie zawiera", "isEmpty": "Jest pusty", @@ -1076,4 +1072,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 a8266563a9..b6e33f0b9a 100644 --- a/frontend/resources/translations/pt-BR.json +++ b/frontend/resources/translations/pt-BR.json @@ -511,13 +511,9 @@ "isComplete": "está completo", "isIncomplted": "está imcompleto" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Está", "isNot": "Não está", - "isEmpty": "Está vazio", - "isNotEmpty": "Não está vazio" - }, - "multiSelectOptionFilter": { "contains": "Contém", "doesNotContain": "Não contém", "isEmpty": "Está vazio", @@ -1211,4 +1207,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 f20c5dba52..ef506dd6ec 100644 --- a/frontend/resources/translations/pt-PT.json +++ b/frontend/resources/translations/pt-PT.json @@ -426,13 +426,9 @@ "isComplete": "está completo", "isIncomplted": "está incompleto" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "É", "isNot": "não é", - "isEmpty": "Está vazia", - "isNotEmpty": "Não está vazio" - }, - "multiSelectOptionFilter": { "contains": "contém", "doesNotContain": "Não contém", "isEmpty": "Está vazia", @@ -856,4 +852,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 2e2e3d4997..c617118ff7 100644 --- a/frontend/resources/translations/ru-RU.json +++ b/frontend/resources/translations/ru-RU.json @@ -528,13 +528,9 @@ "isComplete": "завершено", "isIncomplted": "не завершено" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Является", "isNot": "Не является", - "isEmpty": "Пусто", - "isNotEmpty": "Не пусто" - }, - "multiSelectOptionFilter": { "contains": "Содержит", "doesNotContain": "Не содержит", "isEmpty": "Пусто", diff --git a/frontend/resources/translations/sv-SE.json b/frontend/resources/translations/sv-SE.json index c7791cfea5..054ab9dff3 100644 --- a/frontend/resources/translations/sv-SE.json +++ b/frontend/resources/translations/sv-SE.json @@ -322,13 +322,9 @@ "isComplete": "är komplett", "isIncomplted": "är ofullständig" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Är", "isNot": "Är inte", - "isEmpty": "Är tom", - "isNotEmpty": "Är inte tom" - }, - "multiSelectOptionFilter": { "contains": "Innehåller", "doesNotContain": "Innehåller inte", "isEmpty": "Är tom", @@ -595,4 +591,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/th-TH.json b/frontend/resources/translations/th-TH.json index d535444d65..993846528f 100644 --- a/frontend/resources/translations/th-TH.json +++ b/frontend/resources/translations/th-TH.json @@ -478,13 +478,9 @@ "isComplete": "เสร็จสมบูรณ์", "isIncomplted": "ไม่เสร็จสมบูรณ์" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "เป็น", "isNot": "ไม่เป็น", - "isEmpty": "ว่างเปล่า", - "isNotEmpty": "ไม่ว่างเปล่า" - }, - "multiSelectOptionFilter": { "contains": "ประกอบด้วย", "doesNotContain": "ไม่ประกอบด้วย", "isEmpty": "ว่างเปล่า", diff --git a/frontend/resources/translations/tr-TR.json b/frontend/resources/translations/tr-TR.json index c33713ca61..37c3554e52 100644 --- a/frontend/resources/translations/tr-TR.json +++ b/frontend/resources/translations/tr-TR.json @@ -454,13 +454,9 @@ "isComplete": "Tamamlanmış", "isIncomplted": "Tamamlanmamış" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Şu olan", "isNot": "Şu olmayan", - "isEmpty": "Boş olan", - "isNotEmpty": "Boş olmayan" - }, - "multiSelectOptionFilter": { "contains": "Şunu içeren", "doesNotContain": "Şunu içermeyen", "isEmpty": "Boş olan", @@ -1093,4 +1089,4 @@ "language": "Dil", "font": "Yazı tipi" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/uk-UA.json b/frontend/resources/translations/uk-UA.json index c714e62a88..3c36875f96 100644 --- a/frontend/resources/translations/uk-UA.json +++ b/frontend/resources/translations/uk-UA.json @@ -281,7 +281,6 @@ "auto": "АВТО", "fallback": "Такий же, як і напрямок макету" }, - "themeUpload": { "button": "Завантажити", "uploadTheme": "Завантажити тему", @@ -347,7 +346,6 @@ "exportFileFail": "Помилка експорту файлу!", "export": "Експорт" }, - "user": { "name": "Ім'я", "email": "Електронна пошта", @@ -416,13 +414,9 @@ "isComplete": "є завершено", "isIncomplted": "є незавершено" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "є", "isNot": "не є", - "isEmpty": "порожнє", - "isNotEmpty": "не порожнє" - }, - "multiSelectOptionFilter": { "contains": "Містить", "doesNotContain": "Не містить", "isEmpty": "порожнє", diff --git a/frontend/resources/translations/ur.json b/frontend/resources/translations/ur.json index 0ec8763bcf..1d4f936d37 100644 --- a/frontend/resources/translations/ur.json +++ b/frontend/resources/translations/ur.json @@ -391,7 +391,7 @@ "isComplete": "مکمل ہے", "isIncomplted": "نامکمل ہے" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "ہے", "isNot": "نہیں ہے", "isEmpty": "خالی ہے", diff --git a/frontend/resources/translations/vi-VN.json b/frontend/resources/translations/vi-VN.json index 38fbeaf2c2..9a34912a30 100644 --- a/frontend/resources/translations/vi-VN.json +++ b/frontend/resources/translations/vi-VN.json @@ -491,13 +491,9 @@ "isComplete": "hoàn tất", "isIncomplted": "chưa hoàn tất" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "Là", "isNot": "Không phải", - "isEmpty": "Rỗng", - "isNotEmpty": "Không rỗng" - }, - "multiSelectOptionFilter": { "contains": "Chứa", "doesNotContain": "Không chứa", "isEmpty": "Rỗng", @@ -811,4 +807,4 @@ "font": "Phông chữ", "date": "Ngày" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index 17075f6e7e..f2491cd523 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -531,13 +531,9 @@ "isComplete": "已完成", "isIncomplted": "未完成" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "是", "isNot": "不是", - "isEmpty": "为空", - "isNotEmpty": "不为空" - }, - "multiSelectOptionFilter": { "contains": "包含", "doesNotContain": "不包含", "isEmpty": "为空", diff --git a/frontend/resources/translations/zh-TW.json b/frontend/resources/translations/zh-TW.json index d82976229e..30bb7526eb 100644 --- a/frontend/resources/translations/zh-TW.json +++ b/frontend/resources/translations/zh-TW.json @@ -513,13 +513,9 @@ "isComplete": "已完成", "isIncomplted": "未完成" }, - "singleSelectOptionFilter": { + "selectOptionFilter": { "is": "是", "isNot": "不是", - "isEmpty": "為空", - "isNotEmpty": "不為空" - }, - "multiSelectOptionFilter": { "contains": "包含", "doesNotContain": "不包含", "isEmpty": "為空", @@ -1261,4 +1257,4 @@ "userIcon": "使用者圖示" }, "noLogFiles": "這裡沒有日誌記錄檔案" -} +} \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs index 59261b1920..1643116ccb 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs @@ -8,38 +8,41 @@ use crate::services::{field::SelectOptionIds, filter::ParseFilterData}; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct SelectOptionFilterPB { #[pb(index = 1)] - pub condition: SelectOptionConditionPB, + pub condition: SelectOptionFilterConditionPB, #[pb(index = 2)] pub option_ids: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[derive(Debug, Default, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] -#[derive(Default)] -pub enum SelectOptionConditionPB { +pub enum SelectOptionFilterConditionPB { #[default] OptionIs = 0, OptionIsNot = 1, - OptionIsEmpty = 2, - OptionIsNotEmpty = 3, + OptionContains = 2, + OptionDoesNotContain = 3, + OptionIsEmpty = 4, + OptionIsNotEmpty = 5, } -impl std::convert::From for u32 { - fn from(value: SelectOptionConditionPB) -> Self { +impl From for u32 { + fn from(value: SelectOptionFilterConditionPB) -> Self { value as u32 } } -impl std::convert::TryFrom for SelectOptionConditionPB { +impl TryFrom for SelectOptionFilterConditionPB { type Error = ErrorCode; fn try_from(value: u8) -> Result { match value { - 0 => Ok(SelectOptionConditionPB::OptionIs), - 1 => Ok(SelectOptionConditionPB::OptionIsNot), - 2 => Ok(SelectOptionConditionPB::OptionIsEmpty), - 3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty), + 0 => Ok(SelectOptionFilterConditionPB::OptionIs), + 1 => Ok(SelectOptionFilterConditionPB::OptionIsNot), + 2 => Ok(SelectOptionFilterConditionPB::OptionContains), + 3 => Ok(SelectOptionFilterConditionPB::OptionDoesNotContain), + 4 => Ok(SelectOptionFilterConditionPB::OptionIsEmpty), + 5 => Ok(SelectOptionFilterConditionPB::OptionIsNotEmpty), _ => Err(ErrorCode::InvalidParams), } } @@ -47,8 +50,8 @@ impl std::convert::TryFrom for SelectOptionConditionPB { impl ParseFilterData for SelectOptionFilterPB { fn parse(condition: u8, content: String) -> Self { Self { - condition: SelectOptionConditionPB::try_from(condition) - .unwrap_or(SelectOptionConditionPB::OptionIs), + condition: SelectOptionFilterConditionPB::try_from(condition) + .unwrap_or(SelectOptionFilterConditionPB::OptionIs), option_ids: SelectOptionIds::from_str(&content) .unwrap_or_default() .into_inner(), diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs index 5eb13d037b..d3a2b89883 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs @@ -12,17 +12,16 @@ pub struct TextFilterPB { pub content: String, } -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] -#[derive(Default)] pub enum TextFilterConditionPB { #[default] - Is = 0, - IsNot = 1, - Contains = 2, - DoesNotContain = 3, - StartsWith = 4, - EndsWith = 5, + TextIs = 0, + TextIsNot = 1, + TextContains = 2, + TextDoesNotContain = 3, + TextStartsWith = 4, + TextEndsWith = 5, TextIsEmpty = 6, TextIsNotEmpty = 7, } @@ -38,12 +37,12 @@ impl std::convert::TryFrom for TextFilterConditionPB { fn try_from(value: u8) -> Result { match value { - 0 => Ok(TextFilterConditionPB::Is), - 1 => Ok(TextFilterConditionPB::IsNot), - 2 => Ok(TextFilterConditionPB::Contains), - 3 => Ok(TextFilterConditionPB::DoesNotContain), - 4 => Ok(TextFilterConditionPB::StartsWith), - 5 => Ok(TextFilterConditionPB::EndsWith), + 0 => Ok(TextFilterConditionPB::TextIs), + 1 => Ok(TextFilterConditionPB::TextIsNot), + 2 => Ok(TextFilterConditionPB::TextContains), + 3 => Ok(TextFilterConditionPB::TextDoesNotContain), + 4 => Ok(TextFilterConditionPB::TextStartsWith), + 5 => Ok(TextFilterConditionPB::TextEndsWith), 6 => Ok(TextFilterConditionPB::TextIsEmpty), 7 => Ok(TextFilterConditionPB::TextIsNotEmpty), _ => Err(ErrorCode::InvalidParams), @@ -54,7 +53,8 @@ impl std::convert::TryFrom for TextFilterConditionPB { impl ParseFilterData for TextFilterPB { fn parse(condition: u8, content: String) -> Self { Self { - condition: TextFilterConditionPB::try_from(condition).unwrap_or(TextFilterConditionPB::Is), + condition: TextFilterConditionPB::try_from(condition) + .unwrap_or(TextFilterConditionPB::TextIs), content, } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_filter.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_filter.rs index 9832d05009..3026964d6c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_filter.rs @@ -1,23 +1,26 @@ +use std::str::FromStr; + +use rust_decimal::Decimal; + use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; use crate::services::field::NumberCellFormat; -use rust_decimal::prelude::Zero; -use rust_decimal::Decimal; -use std::str::FromStr; impl NumberFilterPB { pub fn is_visible(&self, cell_data: &NumberCellFormat) -> Option { - let expected_decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); + let expected_decimal = || Decimal::from_str(&self.content).ok(); let strategy = match self.condition { - NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal), - NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal), - NumberFilterConditionPB::GreaterThan => NumberFilterStrategy::GreaterThan(expected_decimal), - NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal), + NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal()?), + NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal()?), + NumberFilterConditionPB::GreaterThan => { + NumberFilterStrategy::GreaterThan(expected_decimal()?) + }, + NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal()?), NumberFilterConditionPB::GreaterThanOrEqualTo => { - NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal) + NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal()?) }, NumberFilterConditionPB::LessThanOrEqualTo => { - NumberFilterStrategy::LessThanOrEqualTo(expected_decimal) + NumberFilterStrategy::LessThanOrEqualTo(expected_decimal()?) }, NumberFilterConditionPB::NumberIsEmpty => NumberFilterStrategy::Empty, NumberFilterConditionPB::NumberIsNotEmpty => NumberFilterStrategy::NotEmpty, diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index 572bfc5021..8ebd0d1db4 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs @@ -128,7 +128,7 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption { cell_data: &::CellData, ) -> bool { let selected_options = self.get_selected_options(cell_data.clone()).select_options; - filter.is_visible(&selected_options, FieldType::MultiSelect) + filter.is_visible(&selected_options).unwrap_or(true) } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs index 148cef5f75..a1ff7e198a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs @@ -1,106 +1,110 @@ -#![allow(clippy::needless_collect)] - -use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; +use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB}; use crate::services::field::SelectOption; impl SelectOptionFilterPB { - pub fn is_visible(&self, selected_options: &[SelectOption], field_type: FieldType) -> bool { - let selected_option_ids: Vec<&String> = - selected_options.iter().map(|option| &option.id).collect(); - match self.condition { - SelectOptionConditionPB::OptionIs => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } + pub fn is_visible(&self, selected_options: &[SelectOption]) -> Option { + let selected_option_ids = selected_options + .iter() + .map(|option| &option.id) + .collect::>(); - if selected_options.is_empty() { - return false; - } + let get_non_empty_expected_options = + || (!self.option_ids.is_empty()).then(|| self.option_ids.clone()); - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - !required_options.is_empty() - }, - FieldType::MultiSelect => { - if self.option_ids.is_empty() { - return true; - } - - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - !required_options.is_empty() - }, - _ => false, + let strategy = match self.condition { + SelectOptionFilterConditionPB::OptionIs => { + SelectOptionFilterStrategy::Is(get_non_empty_expected_options()?) }, - SelectOptionConditionPB::OptionIsNot => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } - - if selected_options.is_empty() { - return false; - } - - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - required_options.is_empty() - }, - FieldType::MultiSelect => { - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - required_options.is_empty() - }, - _ => false, + SelectOptionFilterConditionPB::OptionIsNot => { + SelectOptionFilterStrategy::IsNot(get_non_empty_expected_options()?) }, - SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(), - SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(), + SelectOptionFilterConditionPB::OptionContains => { + SelectOptionFilterStrategy::Contains(get_non_empty_expected_options()?) + }, + SelectOptionFilterConditionPB::OptionDoesNotContain => { + SelectOptionFilterStrategy::DoesNotContain(get_non_empty_expected_options()?) + }, + SelectOptionFilterConditionPB::OptionIsEmpty => SelectOptionFilterStrategy::IsEmpty, + SelectOptionFilterConditionPB::OptionIsNotEmpty => SelectOptionFilterStrategy::IsNotEmpty, + }; + + Some(strategy.filter(&selected_option_ids)) + } +} + +enum SelectOptionFilterStrategy { + Is(Vec), + IsNot(Vec), + Contains(Vec), + DoesNotContain(Vec), + IsEmpty, + IsNotEmpty, +} + +impl SelectOptionFilterStrategy { + fn filter(self, selected_option_ids: &[&String]) -> bool { + match self { + SelectOptionFilterStrategy::Is(option_ids) => { + if selected_option_ids.is_empty() { + return false; + } + + selected_option_ids.len() == option_ids.len() + && selected_option_ids.iter().all(|id| option_ids.contains(id)) + }, + SelectOptionFilterStrategy::IsNot(option_ids) => { + if selected_option_ids.is_empty() { + return true; + } + + selected_option_ids.len() != option_ids.len() + || !selected_option_ids.iter().all(|id| option_ids.contains(id)) + }, + SelectOptionFilterStrategy::Contains(option_ids) => { + if selected_option_ids.is_empty() { + return false; + } + + let required_options = option_ids + .into_iter() + .filter(|id| selected_option_ids.contains(&id)) + .collect::>(); + + !required_options.is_empty() + }, + SelectOptionFilterStrategy::DoesNotContain(option_ids) => { + if selected_option_ids.is_empty() { + return true; + } + + let required_options = option_ids + .into_iter() + .filter(|id| selected_option_ids.contains(&id)) + .collect::>(); + + required_options.is_empty() + }, + SelectOptionFilterStrategy::IsEmpty => selected_option_ids.is_empty(), + SelectOptionFilterStrategy::IsNotEmpty => !selected_option_ids.is_empty(), } } } #[cfg(test)] mod tests { - #![allow(clippy::all)] - use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; + use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB}; use crate::services::field::SelectOption; #[test] fn select_option_filter_is_empty_test() { let option = SelectOption::new("A"); let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsEmpty, + condition: SelectOptionFilterConditionPB::OptionIsEmpty, option_ids: vec![], }; - assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), true); - assert_eq!( - filter.is_visible(&vec![option.clone()], FieldType::SingleSelect), - false, - ); - - assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), true); - assert_eq!( - filter.is_visible(&vec![option], FieldType::MultiSelect), - false, - ); + assert_eq!(filter.is_visible(&[]), Some(true)); + assert_eq!(filter.is_visible(&[option.clone()]), Some(false)); } #[test] @@ -108,157 +112,227 @@ mod tests { let option_1 = SelectOption::new("A"); let option_2 = SelectOption::new("B"); let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNotEmpty, + condition: SelectOptionFilterConditionPB::OptionIsNotEmpty, option_ids: vec![option_1.id.clone(), option_2.id.clone()], }; - assert_eq!( - filter.is_visible(&vec![option_1.clone()], FieldType::SingleSelect), - true - ); - assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), false,); - - assert_eq!( - filter.is_visible(&vec![option_1.clone()], FieldType::MultiSelect), - true - ); - assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), false,); + assert_eq!(filter.is_visible(&[]), Some(false)); + assert_eq!(filter.is_visible(&[option_1.clone()]), Some(true)); } #[test] - fn single_select_option_filter_is_not_test() { + fn select_option_filter_is_test() { let option_1 = SelectOption::new("A"); let option_2 = SelectOption::new("B"); let option_3 = SelectOption::new("C"); + + // no expected options let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], + condition: SelectOptionFilterConditionPB::OptionIs, + option_ids: vec![], }; - - for (options, is_visible) in vec![ - (vec![option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_3.clone()], true), - (vec![option_1.clone(), option_2.clone()], false), + for (options, is_visible) in [ + (vec![], None), + (vec![option_1.clone()], None), + (vec![option_1.clone(), option_2.clone()], None), ] { - assert_eq!( - filter.is_visible(&options, FieldType::SingleSelect), - is_visible - ); + assert_eq!(filter.is_visible(&options), is_visible); } - } - - #[test] - fn single_select_option_filter_is_test() { - let option_1 = SelectOption::new("A"); - let option_2 = SelectOption::new("B"); - let option_3 = SelectOption::new("c"); + // one expected option let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![option_1.id.clone()], }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], false), - (vec![option_3.clone()], false), - (vec![option_1.clone(), option_2.clone()], true), + for (options, is_visible) in [ + (vec![], Some(false)), + (vec![option_1.clone()], Some(true)), + (vec![option_2.clone()], Some(false)), + (vec![option_3.clone()], Some(false)), + (vec![option_1.clone(), option_2.clone()], Some(false)), ] { - assert_eq!( - filter.is_visible(&options, FieldType::SingleSelect), - is_visible - ); + assert_eq!(filter.is_visible(&options), is_visible); } - } - - #[test] - fn single_select_option_filter_is_test2() { - let option_1 = SelectOption::new("A"); - let option_2 = SelectOption::new("B"); + // multiple expected options let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_2.clone()], true), - ] { - assert_eq!( - filter.is_visible(&options, FieldType::SingleSelect), - is_visible - ); - } - } - - #[test] - fn multi_select_option_filter_not_contains_test() { - let option_1 = SelectOption::new("A"); - let option_2 = SelectOption::new("B"); - let option_3 = SelectOption::new("C"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![option_1.id.clone(), option_2.id.clone()], }; - - for (options, is_visible) in vec![ - (vec![option_1.clone(), option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_2.clone()], false), - (vec![option_3.clone()], true), + for (options, is_visible) in [ + (vec![], Some(false)), + (vec![option_1.clone()], Some(false)), + (vec![option_1.clone(), option_2.clone()], Some(true)), ( vec![option_1.clone(), option_2.clone(), option_3.clone()], - false, + Some(false), ), - (vec![], true), ] { - assert_eq!( - filter.is_visible(&options, FieldType::MultiSelect), - is_visible - ); + assert_eq!(filter.is_visible(&options), is_visible); } } + #[test] - fn multi_select_option_filter_contains_test() { + fn select_option_filter_is_not_test() { let option_1 = SelectOption::new("A"); let option_2 = SelectOption::new("B"); let option_3 = SelectOption::new("C"); + // no expected options let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIsNot, + option_ids: vec![], + }; + for (options, is_visible) in [ + (vec![], None), + (vec![option_1.clone()], None), + (vec![option_1.clone(), option_2.clone()], None), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // one expected option + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionIsNot, + option_ids: vec![option_1.id.clone()], + }; + for (options, is_visible) in [ + (vec![], Some(true)), + (vec![option_1.clone()], Some(false)), + (vec![option_2.clone()], Some(true)), + (vec![option_3.clone()], Some(true)), + (vec![option_1.clone(), option_2.clone()], Some(true)), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // multiple expected options + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionIsNot, option_ids: vec![option_1.id.clone(), option_2.id.clone()], }; - for (options, is_visible) in vec![ + for (options, is_visible) in [ + (vec![], Some(true)), + (vec![option_1.clone()], Some(true)), + (vec![option_1.clone(), option_2.clone()], Some(false)), ( vec![option_1.clone(), option_2.clone(), option_3.clone()], - true, + Some(true), ), - (vec![option_2.clone(), option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_3.clone()], true), - (vec![option_3.clone()], false), ] { - assert_eq!( - filter.is_visible(&options, FieldType::MultiSelect), - is_visible - ); + assert_eq!(filter.is_visible(&options), is_visible); } } #[test] - fn multi_select_option_filter_contains_test2() { + fn select_option_filter_contains_test() { let option_1 = SelectOption::new("A"); + let option_2 = SelectOption::new("B"); + let option_3 = SelectOption::new("C"); + let option_4 = SelectOption::new("D"); + // no expected options let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionContains, option_ids: vec![], }; - for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] { - assert_eq!( - filter.is_visible(&options, FieldType::MultiSelect), - is_visible - ); + for (options, is_visible) in [ + (vec![], None), + (vec![option_1.clone()], None), + (vec![option_1.clone(), option_2.clone()], None), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // one expected option + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionContains, + option_ids: vec![option_1.id.clone()], + }; + for (options, is_visible) in [ + (vec![], Some(false)), + (vec![option_1.clone()], Some(true)), + (vec![option_2.clone()], Some(false)), + (vec![option_1.clone(), option_2.clone()], Some(true)), + (vec![option_3.clone(), option_4.clone()], Some(false)), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // multiple expected options + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionContains, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + for (options, is_visible) in [ + (vec![], Some(false)), + (vec![option_1.clone()], Some(true)), + (vec![option_3.clone()], Some(false)), + (vec![option_1.clone(), option_2.clone()], Some(true)), + (vec![option_1.clone(), option_3.clone()], Some(true)), + (vec![option_3.clone(), option_4.clone()], Some(false)), + ( + vec![option_1.clone(), option_3.clone(), option_4.clone()], + Some(true), + ), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + } + + #[test] + fn select_option_filter_does_not_contain_test() { + let option_1 = SelectOption::new("A"); + let option_2 = SelectOption::new("B"); + let option_3 = SelectOption::new("C"); + let option_4 = SelectOption::new("D"); + + // no expected options + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionDoesNotContain, + option_ids: vec![], + }; + for (options, is_visible) in [ + (vec![], None), + (vec![option_1.clone()], None), + (vec![option_1.clone(), option_2.clone()], None), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // one expected option + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionDoesNotContain, + option_ids: vec![option_1.id.clone()], + }; + for (options, is_visible) in [ + (vec![], Some(true)), + (vec![option_1.clone()], Some(false)), + (vec![option_2.clone()], Some(true)), + (vec![option_1.clone(), option_2.clone()], Some(false)), + (vec![option_3.clone(), option_4.clone()], Some(true)), + ] { + assert_eq!(filter.is_visible(&options), is_visible); + } + + // multiple expected options + let filter = SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionDoesNotContain, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + for (options, is_visible) in [ + (vec![], Some(true)), + (vec![option_1.clone()], Some(false)), + (vec![option_3.clone()], Some(true)), + (vec![option_1.clone(), option_2.clone()], Some(false)), + (vec![option_1.clone(), option_3.clone()], Some(false)), + (vec![option_3.clone(), option_4.clone()], Some(true)), + ( + vec![option_1.clone(), option_3.clone(), option_4.clone()], + Some(false), + ), + ] { + assert_eq!(filter.is_visible(&options), is_visible); } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs index 2925dc04ef..fa0745133b 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs @@ -119,7 +119,7 @@ impl TypeOptionCellDataFilter for SingleSelectTypeOption { cell_data: &::CellData, ) -> bool { let selected_options = self.get_selected_options(cell_data.clone()).select_options; - filter.is_visible(&selected_options, FieldType::SingleSelect) + filter.is_visible(&selected_options).unwrap_or(true) } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_filter.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_filter.rs index f684dcc56b..0d966da381 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_filter.rs @@ -5,12 +5,12 @@ impl TextFilterPB { let cell_data = cell_data.as_ref().to_lowercase(); let content = &self.content.to_lowercase(); match self.condition { - TextFilterConditionPB::Is => &cell_data == content, - TextFilterConditionPB::IsNot => &cell_data != content, - TextFilterConditionPB::Contains => cell_data.contains(content), - TextFilterConditionPB::DoesNotContain => !cell_data.contains(content), - TextFilterConditionPB::StartsWith => cell_data.starts_with(content), - TextFilterConditionPB::EndsWith => cell_data.ends_with(content), + TextFilterConditionPB::TextIs => &cell_data == content, + TextFilterConditionPB::TextIsNot => &cell_data != content, + TextFilterConditionPB::TextContains => cell_data.contains(content), + TextFilterConditionPB::TextDoesNotContain => !cell_data.contains(content), + TextFilterConditionPB::TextStartsWith => cell_data.starts_with(content), + TextFilterConditionPB::TextEndsWith => cell_data.ends_with(content), TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(), TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(), } @@ -25,7 +25,7 @@ mod tests { #[test] fn text_filter_equal_test() { let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Is, + condition: TextFilterConditionPB::TextIs, content: "appflowy".to_owned(), }; @@ -37,7 +37,7 @@ mod tests { #[test] fn text_filter_start_with_test() { let text_filter = TextFilterPB { - condition: TextFilterConditionPB::StartsWith, + condition: TextFilterConditionPB::TextStartsWith, content: "appflowy".to_owned(), }; @@ -49,7 +49,7 @@ mod tests { #[test] fn text_filter_end_with_test() { let text_filter = TextFilterPB { - condition: TextFilterConditionPB::EndsWith, + condition: TextFilterConditionPB::TextEndsWith, content: "appflowy".to_owned(), }; @@ -70,7 +70,7 @@ mod tests { #[test] fn text_filter_contain_test() { let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Contains, + condition: TextFilterConditionPB::TextContains, content: "appflowy".to_owned(), }; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs index 58e20aea43..73a0bfc191 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs @@ -1,4 +1,4 @@ -use flowy_database2::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; +use flowy_database2::entities::{FieldType, SelectOptionFilterConditionPB, SelectOptionFilterPB}; use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; @@ -12,7 +12,7 @@ async fn grid_filter_multi_select_is_empty_test() { parent_filter_id: None, field_type: FieldType::MultiSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsEmpty, + condition: SelectOptionFilterConditionPB::OptionIsEmpty, option_ids: vec![], }), changed: None, @@ -30,7 +30,7 @@ async fn grid_filter_multi_select_is_not_empty_test() { parent_filter_id: None, field_type: FieldType::MultiSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNotEmpty, + condition: SelectOptionFilterConditionPB::OptionIsNotEmpty, option_ids: vec![], }), changed: None, @@ -50,12 +50,12 @@ async fn grid_filter_multi_select_is_test() { parent_filter_id: None, field_type: FieldType::MultiSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![options.remove(0).id, options.remove(0).id], }), changed: None, }, - AssertNumberOfVisibleRows { expected: 5 }, + AssertNumberOfVisibleRows { expected: 1 }, ]; test.run_scripts(scripts).await; } @@ -70,12 +70,12 @@ async fn grid_filter_multi_select_is_test2() { parent_filter_id: None, field_type: FieldType::MultiSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![options.remove(1).id], }), changed: None, }, - AssertNumberOfVisibleRows { expected: 4 }, + AssertNumberOfVisibleRows { expected: 1 }, ]; test.run_scripts(scripts).await; } @@ -90,7 +90,7 @@ async fn grid_filter_single_select_is_empty_test() { parent_filter_id: None, field_type: FieldType::SingleSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsEmpty, + condition: SelectOptionFilterConditionPB::OptionIsEmpty, option_ids: vec![], }), changed: Some(FilterRowChanged { @@ -115,7 +115,7 @@ async fn grid_filter_single_select_is_test() { parent_filter_id: None, field_type: FieldType::SingleSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![options.remove(0).id], }), changed: Some(FilterRowChanged { @@ -142,7 +142,7 @@ async fn grid_filter_single_select_is_test2() { parent_filter_id: None, field_type: FieldType::SingleSelect, data: BoxAny::new(SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, + condition: SelectOptionFilterConditionPB::OptionIs, option_ids: vec![option.id.clone()], }), changed: Some(FilterRowChanged { @@ -169,3 +169,43 @@ async fn grid_filter_single_select_is_test2() { ]; test.run_scripts(scripts).await; } + +#[tokio::test] +async fn grid_filter_multi_select_contains_test() { + let mut test = DatabaseFilterTest::new().await; + let field = test.get_first_field(FieldType::MultiSelect); + let mut options = test.get_multi_select_type_option(&field.id); + let scripts = vec![ + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionContains, + option_ids: vec![options.remove(0).id, options.remove(0).id], + }), + changed: None, + }, + AssertNumberOfVisibleRows { expected: 5 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_multi_select_contains_test2() { + let mut test = DatabaseFilterTest::new().await; + let field = test.get_first_field(FieldType::MultiSelect); + let mut options = test.get_multi_select_type_option(&field.id); + let scripts = vec![ + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionFilterConditionPB::OptionContains, + option_ids: vec![options.remove(1).id], + }), + changed: None, + }, + AssertNumberOfVisibleRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs index 076dc267dd..600f4342fa 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs @@ -70,7 +70,7 @@ async fn grid_filter_is_text_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::Is, + condition: TextFilterConditionPB::TextIs, content: "A".to_string(), }), changed: Some(FilterRowChanged { @@ -88,7 +88,7 @@ async fn grid_filter_contain_text_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::Contains, + condition: TextFilterConditionPB::TextContains, content: "A".to_string(), }), changed: Some(FilterRowChanged { @@ -109,7 +109,7 @@ async fn grid_filter_contain_text_test2() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::Contains, + condition: TextFilterConditionPB::TextContains, content: "A".to_string(), }), changed: Some(FilterRowChanged { @@ -137,7 +137,7 @@ async fn grid_filter_does_not_contain_text_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::DoesNotContain, + condition: TextFilterConditionPB::TextDoesNotContain, content: "AB".to_string(), }), changed: Some(FilterRowChanged { @@ -155,7 +155,7 @@ async fn grid_filter_start_with_text_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::StartsWith, + condition: TextFilterConditionPB::TextStartsWith, content: "A".to_string(), }), changed: Some(FilterRowChanged { @@ -174,7 +174,7 @@ async fn grid_filter_ends_with_text_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::EndsWith, + condition: TextFilterConditionPB::TextEndsWith, content: "A".to_string(), }), changed: None, @@ -192,7 +192,7 @@ async fn grid_update_text_filter_test() { parent_filter_id: None, field_type: FieldType::RichText, data: BoxAny::new(TextFilterPB { - condition: TextFilterConditionPB::EndsWith, + condition: TextFilterConditionPB::TextEndsWith, content: "A".to_string(), }), changed: Some(FilterRowChanged { @@ -210,7 +210,7 @@ async fn grid_update_text_filter_test() { let scripts = vec![ UpdateTextFilter { filter, - condition: TextFilterConditionPB::Is, + condition: TextFilterConditionPB::TextIs, content: "A".to_string(), changed: Some(FilterRowChanged { showing_num_of_rows: 0,