Browse Source

Merge pull request #279 from Hari-07/language_switcher

Language switcher
Nathan.fooo 3 năm trước cách đây
mục cha
commit
68d4c6dbdb

+ 6 - 3
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"
     }
   }
-}
+}

+ 5 - 2
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",

+ 5 - 2
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",

+ 19 - 5
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": "夜间模式"
+    }
   }
-}
+}

+ 21 - 12
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<AppearanceSettingModel, AppTheme>(
             (value) => value.theme,
           );
+          AppLanguage language = context.select<AppearanceSettingModel, AppLanguage>(
+            (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,
+              );
+            },
           );
         },
       );

+ 15 - 4
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<void> 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();
     }

+ 38 - 32
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<SettingsDialog> {
 
   @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<AppearanceSettingModel>(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],
+              )
+            ],
+          ),
         ),
       ),
     );

+ 0 - 3
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(

+ 40 - 1
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<LanguageSelectorDropdown> createState() => _LanguageSelectorDropdownState();
+}
+
+class _LanguageSelectorDropdownState extends State<LanguageSelectorDropdown> {
+  @override
+  Widget build(BuildContext context) {
+    return DropdownButton<AppLanguage>(
+      value: context.read<AppearanceSettingModel>().language,
+      onChanged: (val) {
+        setState(() {
+          context.read<AppearanceSettingModel>().setLanguage(context, val!);
+        });
+      },
+      autofocus: true,
+      items: AppLanguage.values.map((language) {
+        return DropdownMenuItem<AppLanguage>(
+          value: language,
+          child: Text(describeEnum(language)),
+        );
+      }).toList(),
+    );
   }
 }

+ 17 - 8
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<DocShareBloc, DocShareState>(
           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<AppearanceSettingModel>(context, listen: true),
+              child: Selector<AppearanceSettingModel, AppLanguage>(
+                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)),
+                ),
+              ),
             );
           },
         ),

+ 3 - 3
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<FlowyColorButton> {
   late bool _isWhitebackground;
 
   Style get _selectionStyle => widget.controller.getSelectionStyle();
-  final tooltipText = 'Highlight';
 
   void _didChangeEditingValue() {
     setState(() {
@@ -93,7 +93,7 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
         : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
 
     return Tooltip(
-      message: tooltipText,
+      message: LocaleKeys.toolbar_highlight.tr(),
       showDuration: Duration.zero,
       child: QuillIconButton(
         highlightElevation: 0,

+ 3 - 2
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<FlowyHeaderStyleButton> {
         // 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: () {

+ 17 - 3
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<AppTheme>();
     return Row(children: [
-      SizedBox(width: 16, height: 16, child: svg("home/trash", color: theme.iconColor)),
+      ChangeNotifierProvider.value(
+        value: Provider.of<AppearanceSettingModel>(context, listen: true),
+        child: Selector<AppearanceSettingModel, AppTheme>(
+          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<AppearanceSettingModel>(context, listen: true),
+        child: Selector<AppearanceSettingModel, AppLanguage>(
+          selector: (ctx, notifier) => notifier.language,
+          builder: (ctx, _, child) => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12),
+        ),
+      ),
     ]);
   }
 }

+ 3 - 1
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(

+ 7 - 0
frontend/app_flowy/macos/Runner/Info.plist

@@ -33,5 +33,12 @@
 	<string>MainMenu</string>
 	<key>NSPrincipalClass</key>
 	<string>NSApplication</string>
+    <key>CFBundleLocalizations</key>
+    <array>
+        <string>en</string>
+        <string>fr</string>
+        <string>it</string>
+        <string>zh</string>
+    </array>
 </dict>
 </plist>

+ 47 - 0
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');
+  }
+}