diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 0b461e321c..9294fb3f0e 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -88,7 +88,9 @@ "bulletList": "Bulleted List", "checkList": "Check List", "inlineCode": "Inline Code", - "quote": "Quote Block" + "quote": "Quote Block", + "header": "Header", + "highlight": "Highlight" }, "tooltip": { "lightMode": "Switch to Light mode", @@ -132,11 +134,12 @@ "title": "Settings", "menu": { "appearance": "Appearance", - "language": "Language" + "language": "Language", + "open": "Open Settings" }, "appearance": { "lightLabel": "Light Mode", "darkLabel": "Dark Mode" } } -} \ No newline at end of file +} diff --git a/frontend/app_flowy/assets/translations/fr-CA.json b/frontend/app_flowy/assets/translations/fr-CA.json index 04018b08c4..ae13e352b0 100644 --- a/frontend/app_flowy/assets/translations/fr-CA.json +++ b/frontend/app_flowy/assets/translations/fr-CA.json @@ -88,7 +88,9 @@ "bulletList": "Liste à puces", "checkList": "Liste de contrôle", "inlineCode": "Code en ligne", - "quote": "Citation" + "quote": "Citation", + "header": "en-tête", + "highlight": "Surligner" }, "tooltip": { "lightMode": "Aller en mode claire", @@ -132,7 +134,8 @@ "title": "Réglages", "menu": { "appearance": "Apparence", - "language": "Langue" + "language": "Langue", + "open": "ouvrir les paramètres" }, "appearance": { "lightLabel": "Mode claire", diff --git a/frontend/app_flowy/assets/translations/it-IT.json b/frontend/app_flowy/assets/translations/it-IT.json index eea093afcc..5695b0a8bd 100644 --- a/frontend/app_flowy/assets/translations/it-IT.json +++ b/frontend/app_flowy/assets/translations/it-IT.json @@ -88,7 +88,9 @@ "bulletList": "Lista a punti", "checkList": "Lista Controllo", "inlineCode": "Codice in linea", - "quote": "Cita Blocco" + "quote": "Cita Blocco", + "header": "intestazione", + "highlight": "Evidenziare" }, "tooltip": { "lightMode": "Passa alla modalità Chiara", @@ -132,7 +134,8 @@ "title": "Impostazioni", "menu": { "appearance": "Aspetto", - "language": "Lingua" + "language": "Lingua", + "open": "aprire le impostazioni" }, "appearance": { "lightLabel": "Modalità Chiara", diff --git a/frontend/app_flowy/assets/translations/zh-CN.json b/frontend/app_flowy/assets/translations/zh-CN.json index b57bcd8c36..0dd0acd0fb 100644 --- a/frontend/app_flowy/assets/translations/zh-CN.json +++ b/frontend/app_flowy/assets/translations/zh-CN.json @@ -88,11 +88,13 @@ "bulletList": "无序列表", "checkList": "任务列表", "inlineCode": "内联代码", - "quote": "块引用" + "quote": "块引用", + "header": "标题", + "highlight": "高亮" }, - "tooltip":{ - "lightMode" : "切换到灯光模式", - "darkMode" : "切换到暗模式" + "tooltip": { + "lightMode": "切换到灯光模式", + "darkMode": "切换到暗模式" }, "contactsPage": { "title": "联系人", @@ -127,5 +129,17 @@ "instruction3": "进入下面的链接,然后输入上面的代码:", "instruction4": "完成注册后,点击下面的按钮:" } + }, + "settings": { + "title": "设置", + "menu": { + "appearance": "外观", + "language": "语言", + "open": "打开设置" + }, + "appearance": { + "lightLabel": "日间模式", + "darkLabel": "夜间模式" + } } -} \ No newline at end of file +} diff --git a/frontend/app_flowy/lib/startup/tasks/application_widget.dart b/frontend/app_flowy/lib/startup/tasks/application_widget.dart index 2454ca7e07..e99509afad 100644 --- a/frontend/app_flowy/lib/startup/tasks/application_widget.dart +++ b/frontend/app_flowy/lib/startup/tasks/application_widget.dart @@ -2,6 +2,7 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/user/infrastructure/repos/user_setting_repo.dart'; import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/language.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -61,19 +62,27 @@ class ApplicationWidget extends StatelessWidget { AppTheme theme = context.select( (value) => value.theme, ); + AppLanguage language = context.select( + (value) => value.language, + ); - return Provider.value( - value: theme, - child: MaterialApp( - builder: overlayManagerBuilder(), - debugShowCheckedModeBanner: false, - theme: theme.themeData, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - navigatorKey: AppGlobals.rootNavKey, - home: child, - ), + return MultiProvider( + providers: [ + Provider.value(value: theme), + Provider.value(value: language), + ], + builder: (context, _) { + return MaterialApp( + builder: overlayManagerBuilder(), + debugShowCheckedModeBanner: false, + theme: theme.themeData, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: localeFromLanguageName(language), + navigatorKey: AppGlobals.rootNavKey, + home: child, + ); + }, ); }, ); diff --git a/frontend/app_flowy/lib/workspace/application/appearance.dart b/frontend/app_flowy/lib/workspace/application/appearance.dart index 42e8e591cd..187491cf3f 100644 --- a/frontend/app_flowy/lib/workspace/application/appearance.dart +++ b/frontend/app_flowy/lib/workspace/application/appearance.dart @@ -1,15 +1,22 @@ import 'package:app_flowy/user/infrastructure/repos/user_setting_repo.dart'; import 'package:equatable/equatable.dart'; import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/language.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_setting.pb.dart'; import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; class AppearanceSettingModel extends ChangeNotifier with EquatableMixin { AppearanceSettings setting; AppTheme _theme; + AppLanguage _language; + + AppearanceSettingModel(this.setting) + : _theme = AppTheme.fromName(name: setting.theme), + _language = languageFromString(setting.language); - AppearanceSettingModel(this.setting) : _theme = AppTheme.fromName(name: setting.theme); AppTheme get theme => _theme; + AppLanguage get language => _language; Future save() async { await UserSettingReppsitory().setAppearanceSettings(setting); @@ -31,9 +38,13 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin { } } - void setLanguage(String language) { - if (setting.language != language) { - setting.language = language; + void setLanguage(BuildContext context, AppLanguage language) { + String languageString = stringFromLanguageName(language); + + if (setting.language != languageString) { + context.setLocale(localeFromLanguageName(language)); + _language = language; + setting.language = languageString; notifyListeners(); save(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart index 2fee55cc33..8c7bb8f494 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,9 +1,11 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_appearance_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class SettingsDialog extends StatefulWidget { const SettingsDialog({Key? key}) : super(key: key); @@ -22,41 +24,45 @@ class _SettingsDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - title: Text( - LocaleKeys.settings_title.tr(), - style: const TextStyle( - fontWeight: FontWeight.bold, + return ChangeNotifierProvider.value( + value: Provider.of(context, listen: true), + child: AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - ), - content: ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: 600, + title: Text( + LocaleKeys.settings_title.tr(), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 1, - child: SettingsMenu( - changeSelectedIndex: (index) { - setState(() { - _selectedViewIndex = index; - }); - }, - currentIndex: _selectedViewIndex, + content: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 600, + minWidth: 600, + maxWidth: 1000, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 200, + child: SettingsMenu( + changeSelectedIndex: (index) { + setState(() { + _selectedViewIndex = index; + }); + }, + currentIndex: _selectedViewIndex, + ), ), - ), - const VerticalDivider(), - const SizedBox(width: 10), - Expanded( - flex: 4, - child: settingsViews[_selectedViewIndex], - ) - ], + const VerticalDivider(), + const SizedBox(width: 10), + Expanded( + child: settingsViews[_selectedViewIndex], + ) + ], + ), ), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index 9b42a0437b..e03f1fbc3b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -16,9 +16,6 @@ class SettingsAppearanceView extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( - height: 15, - ), Row( children: [ Text( diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart index 4681fcc2b8..34beba38f5 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart @@ -1,10 +1,49 @@ +import 'package:app_flowy/workspace/application/appearance.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flowy_infra/language.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class SettingsLanguageView extends StatelessWidget { const SettingsLanguageView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return const Center(child: Text('Work In Progress')); + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [LanguageSelectorDropdown()], + ), + ); + } +} + +class LanguageSelectorDropdown extends StatefulWidget { + const LanguageSelectorDropdown({ + Key? key, + }) : super(key: key); + + @override + State createState() => _LanguageSelectorDropdownState(); +} + +class _LanguageSelectorDropdownState extends State { + @override + Widget build(BuildContext context) { + return DropdownButton( + value: context.read().language, + onChanged: (val) { + setState(() { + context.read().setLanguage(context, val!); + }); + }, + autofocus: true, + items: AppLanguage.values.map((language) { + return DropdownMenuItem( + value: language, + child: Text(describeEnum(language)), + ); + }).toList(), + ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart index 73cd1f961a..a801aea850 100644 --- a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart @@ -1,4 +1,5 @@ import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/application/doc/share_bloc.dart'; import 'package:app_flowy/workspace/domain/i_view.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; @@ -7,6 +8,7 @@ import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/language.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -20,6 +22,7 @@ import 'package:dartz/dartz.dart' as dartz; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:clipboard/clipboard.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:provider/provider.dart'; import 'doc_page.dart'; @@ -171,14 +174,20 @@ class DococumentShareButton extends StatelessWidget { }, child: BlocBuilder( builder: (context, state) { - return RoundedTextButton( - title: 'shareAction.buttonText'.tr(), - height: 30, - width: buttonWidth, - fontSize: 12, - borderRadius: Corners.s6Border, - color: Colors.lightBlue, - onPressed: () => _showActionList(context, Offset(-(buttonWidth / 2), 10)), + return ChangeNotifierProvider.value( + value: Provider.of(context, listen: true), + child: Selector( + selector: (ctx, notifier) => notifier.language, + builder: (ctx, _, child) => RoundedTextButton( + title: LocaleKeys.shareAction_buttonText.tr(), + height: 30, + width: buttonWidth, + fontSize: 12, + borderRadius: Corners.s6Border, + color: Colors.lightBlue, + onPressed: () => _showActionList(context, Offset(-(buttonWidth / 2), 10)), + ), + ), ); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/color_picker.dart b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/color_picker.dart index da91a1d2cf..793399989c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/color_picker.dart +++ b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/color_picker.dart @@ -3,7 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/utils/color.dart'; - +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'toolbar_icon_button.dart'; class FlowyColorButton extends StatefulWidget { @@ -33,7 +34,6 @@ class _FlowyColorButtonState extends State { late bool _isWhitebackground; Style get _selectionStyle => widget.controller.getSelectionStyle(); - final tooltipText = 'Highlight'; void _didChangeEditingValue() { setState(() { @@ -93,7 +93,7 @@ class _FlowyColorButtonState extends State { : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor); return Tooltip( - message: tooltipText, + message: LocaleKeys.toolbar_highlight.tr(), showDuration: Duration.zero, child: QuillIconButton( highlightElevation: 0, diff --git a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/header_button.dart b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/header_button.dart index dbe3b413f9..427c5cf559 100644 --- a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/header_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/header_button.dart @@ -1,7 +1,8 @@ import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter/material.dart'; - +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'toolbar_icon_button.dart'; class FlowyHeaderStyleButton extends StatefulWidget { @@ -50,7 +51,7 @@ class _FlowyHeaderStyleButtonState extends State { // final child = // _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1'); - final headerTitle = 'Header ${index + 1}'; + final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}"; final _isToggled = _valueToText[_value] == _valueString[index]; return ToolbarIconButton( onPressed: () { diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart index 65aca331e9..52c82b97d7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart @@ -1,9 +1,11 @@ import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/presentation/stack_page/trash/trash_page.dart'; import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/language.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; @@ -29,11 +31,23 @@ class MenuTrash extends StatelessWidget { } Widget _render(BuildContext context) { - final theme = context.watch(); return Row(children: [ - SizedBox(width: 16, height: 16, child: svg("home/trash", color: theme.iconColor)), + ChangeNotifierProvider.value( + value: Provider.of(context, listen: true), + child: Selector( + selector: (ctx, notifier) => notifier.theme, + builder: (ctx, theme, child) => + SizedBox(width: 16, height: 16, child: svg("home/trash", color: theme.iconColor)), + ), + ), const HSpace(6), - FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12), + ChangeNotifierProvider.value( + value: Provider.of(context, listen: true), + child: Selector( + selector: (ctx, notifier) => notifier.language, + builder: (ctx, _, child) => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12), + ), + ), ]); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_user.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_user.dart index 38d760d51e..77fee5ac7c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_user.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_user.dart @@ -7,6 +7,8 @@ import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/protobuf.dart' show UserProfile; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; class MenuUser extends StatelessWidget { final UserProfile user; @@ -63,7 +65,7 @@ class MenuUser extends StatelessWidget { Widget _renderSettingsButton(BuildContext context) { return Tooltip( - message: 'Open Settings', + message: LocaleKeys.settings_menu_open.tr(), child: IconButton( onPressed: () { showDialog( diff --git a/frontend/app_flowy/macos/Runner/Info.plist b/frontend/app_flowy/macos/Runner/Info.plist index 90a8d29001..c5650a4313 100644 --- a/frontend/app_flowy/macos/Runner/Info.plist +++ b/frontend/app_flowy/macos/Runner/Info.plist @@ -33,5 +33,12 @@ MainMenu NSPrincipalClass NSApplication + CFBundleLocalizations + + en + fr + it + zh + diff --git a/frontend/app_flowy/packages/flowy_infra/lib/language.dart b/frontend/app_flowy/packages/flowy_infra/lib/language.dart new file mode 100644 index 0000000000..13ee652501 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_infra/lib/language.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +enum AppLanguage { + english, + chinese, + italian, + french, +} + +String stringFromLanguageName(AppLanguage language) { + switch (language) { + case AppLanguage.english: + return "en"; + case AppLanguage.chinese: + return "ch"; + case AppLanguage.italian: + return "it"; + case AppLanguage.french: + return "fr"; + } +} + +AppLanguage languageFromString(String name) { + AppLanguage language = AppLanguage.english; + if (name == "ch") { + language = AppLanguage.chinese; + } else if (name == "it") { + language = AppLanguage.italian; + } else if (name == "fr") { + language = AppLanguage.french; + } + + return language; +} + +Locale localeFromLanguageName(AppLanguage language) { + switch (language) { + case AppLanguage.english: + return const Locale('en'); + case AppLanguage.chinese: + return const Locale('zh', 'CN'); + case AppLanguage.italian: + return const Locale('it', 'IT'); + case AppLanguage.french: + return const Locale('fr', 'CA'); + } +}