Преглед на файлове

Merge pull request #129 from irfanbacker/i18n-patch

Add i18n support
AppFlowy.IO преди 3 години
родител
ревизия
e28b1ab8b3
променени са 29 файла, в които са добавени 601 реда и са изтрити 90 реда
  1. 122 0
      frontend/app_flowy/assets/translations/en.json
  2. 4 0
      frontend/app_flowy/ios/Runner/Info.plist
  3. 140 0
      frontend/app_flowy/lib/generated/codegen_loader.g.dart
  4. 108 0
      frontend/app_flowy/lib/generated/locale_keys.g.dart
  5. 5 1
      frontend/app_flowy/lib/main.dart
  6. 12 1
      frontend/app_flowy/lib/startup/tasks/application_widget.dart
  7. 5 3
      frontend/app_flowy/lib/user/application/sign_up_bloc.dart
  8. 11 9
      frontend/app_flowy/lib/user/presentation/sign_in_screen.dart
  9. 11 9
      frontend/app_flowy/lib/user/presentation/sign_up_screen.dart
  10. 13 11
      frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart
  11. 4 2
      frontend/app_flowy/lib/user/presentation/welcome_screen.dart
  12. 4 2
      frontend/app_flowy/lib/workspace/domain/edit_action/app_edit.dart
  13. 5 3
      frontend/app_flowy/lib/workspace/domain/edit_action/view_edit.dart
  14. 3 1
      frontend/app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart
  15. 3 1
      frontend/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart
  16. 6 4
      frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart
  17. 6 3
      frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/banner.dart
  18. 13 11
      frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart
  19. 6 4
      frontend/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart
  20. 5 3
      frontend/app_flowy/lib/workspace/presentation/stack_page/trash/widget/trash_header.dart
  21. 5 4
      frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart
  22. 5 7
      frontend/app_flowy/lib/workspace/presentation/widgets/edit_pannel/edit_pannel.dart
  23. 5 3
      frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart
  24. 4 2
      frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/create_button.dart
  25. 7 3
      frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart
  26. 3 1
      frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart
  27. 3 1
      frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart
  28. 76 1
      frontend/app_flowy/pubspec.lock
  29. 7 0
      frontend/app_flowy/pubspec.yaml

+ 122 - 0
frontend/app_flowy/assets/translations/en.json

@@ -0,0 +1,122 @@
+{
+  "appName": "Appflowy",
+  "defaultUsername": "Me",
+  "welcomeText": "Welcome to @:appName",
+  "githubStarText": "Star on GitHub",
+  "subscribeNewsletterText": "Subscribe to Newsletter",
+  "letsGoButtonText": "Let's Go",
+  "title": "Title",
+  "signUp": {
+    "buttonText": "Sign Up",
+    "title": "Sign Up to @:appName",
+    "getStartedText": "Get Started",
+    "emptyPasswordError": "Password can't be empty",
+    "repeatPasswordEmptyError": "Repeat password can't be empty",
+    "unmatchedPasswordError": "Repeat password is not the same as password",
+    "alreadyHaveAnAccount": "Already have an account?",
+    "emailHint": "Email",
+    "passwordHint": "Password",
+    "repeatPasswordHint": "Repeat password"
+  },
+  "signIn": {
+    "loginTitle": "Login to @:appName",
+    "loginButtonText": "Login",
+    "buttonText": "Sign In",
+    "forgotPassword": "Forgot Password?",
+    "emailHint": "Email",
+    "passwordHint": "Password",
+    "dontHaveAnAccount": "Don't have an account?",
+    "repeatPasswordEmptyError": "Repeat password can't be empty",
+    "unmatchedPasswordError": "Repeat password is not the same as password"
+  },
+  "workspace": {
+    "create": "Create workspace",
+    "hint": "workspace",
+    "notFoundError": "Workspace not found"
+  },
+  "shareAction": {
+    "buttonText": "Share",
+    "workInProgress": "Work in progress",
+    "markdown": "Markdown",
+    "copyLink": "Copy Link"
+  },
+  "disclosureAction": {
+    "rename": "Rename",
+    "delete": "Delete",
+    "duplicate": "Duplicate"
+  },
+  "blankPageTitle": "Blank page",
+  "newPageText": "New page",
+  "trash": {
+    "text": "Trash",
+    "restoreAll": "Restore All",
+    "deleteAll": "Delete All",
+    "pageHeader": {
+      "fileName": "File name",
+      "lastModified": "Last Modified",
+      "created": "Created"
+    }
+  },
+  "deletePagePrompt": {
+    "text": "This page is in Trash",
+    "restore": "Restore page",
+    "deletePermanent": "Delete permanently"
+  },
+  "dialogCreatePageNameHint": "Page name",
+  "questionBubble": {
+    "whatsNew": "What's new?",
+    "help": "Help & Support"
+  },
+  "menuAppHeader": {
+    "addPageTooltip": "Quickly add a page inside",
+    "defaultNewPageName": "Untitles",
+    "renameDialog": "Rename"
+  },
+  "toolbar": {
+    "undo": "Undo",
+    "redo": "Redo",
+    "bold": "Bold",
+    "italic": "Italic",
+    "underline": "Underline",
+    "strike": "Strikethrough",
+    "numList": "Numbered List",
+    "bulletList": "Bulleted List",
+    "checkList": "Check List",
+    "inlineCode": "Inline Code",
+    "quote": "Quote Block"
+  },
+  "contactsPage": {
+    "title": "Contacts",
+    "whatsHappening": "What's happening this week?",
+    "addContact": "Add Contact",
+    "editContact": "Edit Contact"
+  },
+  "button": {
+    "OK": "OK",
+    "Cancel": "Cancel",
+    "signIn": "Sign In",
+    "signOut": "Sign Out",
+    "complete": "Complete",
+    "save": "Save"
+  },
+  "label": {
+    "welcome": "Welcome!",
+    "firstName": "First Name",
+    "middleName": "Middle Name",
+    "lastName": "Last Name",
+    "stepX": "Step {X}"
+  },
+  "oAuth": {
+    "err": {
+      "failedTitle": "Unable to connect to your account.",
+      "failedMsg": "Please make sure you've completed the sign-in process in your browser."
+    },
+    "google": {
+      "title": "GOOGLE SIGN-IN",
+      "instruction1": "In order to import your Google Contacts, you'll need to authorize this application using your web browser.",
+      "instruction2": "Copy this code to your clipboard by clicking the icon or selecting the text:",
+      "instruction3": "Navigate to the following link in your web browser, and enter the above code:",
+      "instruction4": "Press the button below when you've completed signup:"
+    }
+  }
+}

+ 4 - 0
frontend/app_flowy/ios/Runner/Info.plist

@@ -41,5 +41,9 @@
 	</array>
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<false/>
+	<key>CFBundleLocalizations</key>
+	<array>
+		<string>en</string>
+	</array>
 </dict>
 </plist>

+ 140 - 0
frontend/app_flowy/lib/generated/codegen_loader.g.dart

@@ -0,0 +1,140 @@
+// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
+
+// ignore_for_file: prefer_single_quotes
+
+import 'dart:ui';
+
+import 'package:easy_localization/easy_localization.dart' show AssetLoader;
+
+class CodegenLoader extends AssetLoader{
+  const CodegenLoader();
+
+  @override
+  Future<Map<String, dynamic>> load(String fullPath, Locale locale ) {
+    return Future.value(mapLocales[locale.toString()]);
+  }
+
+  static const Map<String,dynamic> en = {
+  "appName": "Appflowy",
+  "defaultUsername": "Me",
+  "welcomeText": "Welcome to @:appName",
+  "githubStarText": "Star on GitHub",
+  "subscribeNewsletterText": "Subscribe to Newsletter",
+  "letsGoButtonText": "Let's Go",
+  "title": "Title",
+  "signUp": {
+    "buttonText": "Sign Up",
+    "title": "Sign Up to @:appName",
+    "getStartedText": "Get Started",
+    "emptyPasswordError": "Password can't be empty",
+    "repeatPasswordEmptyError": "Repeat password can't be empty",
+    "unmatchedPasswordError": "Repeat password is not the same as password",
+    "alreadyHaveAnAccount": "Already have an account?",
+    "emailHint": "Email",
+    "passwordHint": "Password",
+    "repeatPasswordHint": "Repeat password"
+  },
+  "signIn": {
+    "loginTitle": "Login to @:appName",
+    "loginButtonText": "Login",
+    "buttonText": "Sign In",
+    "forgotPassword": "Forgot Password?",
+    "emailHint": "Email",
+    "passwordHint": "Password",
+    "dontHaveAnAccount": "Don't have an account?",
+    "repeatPasswordEmptyError": "Repeat password can't be empty",
+    "unmatchedPasswordError": "Repeat password is not the same as password"
+  },
+  "workspace": {
+    "create": "Create workspace",
+    "hint": "workspace",
+    "notFoundError": "Workspace not found"
+  },
+  "shareAction": {
+    "buttonText": "Share",
+    "workInProgress": "Work in progress",
+    "markdown": "Markdown",
+    "copyLink": "Copy Link"
+  },
+  "disclosureAction": {
+    "rename": "Rename",
+    "delete": "Delete",
+    "duplicate": "Duplicate"
+  },
+  "blankPageTitle": "Blank page",
+  "newPageText": "New page",
+  "trash": {
+    "text": "Trash",
+    "restoreAll": "Restore All",
+    "deleteAll": "Delete All",
+    "pageHeader": {
+      "fileName": "File name",
+      "lastModified": "Last Modified",
+      "created": "Created"
+    }
+  },
+  "deletePagePrompt": {
+    "text": "This page is in Trash",
+    "restore": "Restore page",
+    "deletePermanent": "Delete permanently"
+  },
+  "dialogCreatePageNameHint": "Page name",
+  "questionBubble": {
+    "whatsNew": "What's new?",
+    "help": "Help & Support"
+  },
+  "menuAppHeader": {
+    "addPageTooltip": "Quickly add a page inside",
+    "defaultNewPageName": "Untitles",
+    "renameDialog": "Rename"
+  },
+  "toolbar": {
+    "undo": "Undo",
+    "redo": "Redo",
+    "bold": "Bold",
+    "italic": "Italic",
+    "underline": "Underline",
+    "strike": "Strikethrough",
+    "numList": "Numbered List",
+    "bulletList": "Bulleted List",
+    "checkList": "Check List",
+    "inlineCode": "Inline Code",
+    "quote": "Quote Block"
+  },
+  "contactsPage": {
+    "title": "Contacts",
+    "whatsHappening": "What's happening this week?",
+    "addContact": "Add Contact",
+    "editContact": "Edit Contact"
+  },
+  "button": {
+    "OK": "OK",
+    "Cancel": "Cancel",
+    "signIn": "Sign In",
+    "signOut": "Sign Out",
+    "complete": "Complete",
+    "save": "Save"
+  },
+  "label": {
+    "welcome": "Welcome!",
+    "firstName": "First Name",
+    "middleName": "Middle Name",
+    "lastName": "Last Name",
+    "stepX": "Step {X}"
+  },
+  "oAuth": {
+    "err": {
+      "failedTitle": "Unable to connect to your account.",
+      "failedMsg": "Please make sure you've completed the sign-in process in your browser."
+    },
+    "google": {
+      "title": "GOOGLE SIGN-IN",
+      "instruction1": "In order to import your Google Contacts, you'll need to authorize this application using your web browser.",
+      "instruction2": "Copy this code to your clipboard by clicking the icon or selecting the text:",
+      "instruction3": "Navigate to the following link in your web browser, and enter the above code:",
+      "instruction4": "Press the button below when you've completed signup:"
+    }
+  }
+};
+static const Map<String, Map<String,dynamic>> mapLocales = {"en": en};
+}

+ 108 - 0
frontend/app_flowy/lib/generated/locale_keys.g.dart

@@ -0,0 +1,108 @@
+// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
+
+abstract class  LocaleKeys {
+  static const appName = 'appName';
+  static const defaultUsername = 'defaultUsername';
+  static const welcomeText = 'welcomeText';
+  static const githubStarText = 'githubStarText';
+  static const subscribeNewsletterText = 'subscribeNewsletterText';
+  static const letsGoButtonText = 'letsGoButtonText';
+  static const title = 'title';
+  static const signUp_buttonText = 'signUp.buttonText';
+  static const signUp_title = 'signUp.title';
+  static const signUp_getStartedText = 'signUp.getStartedText';
+  static const signUp_emptyPasswordError = 'signUp.emptyPasswordError';
+  static const signUp_repeatPasswordEmptyError = 'signUp.repeatPasswordEmptyError';
+  static const signUp_unmatchedPasswordError = 'signUp.unmatchedPasswordError';
+  static const signUp_alreadyHaveAnAccount = 'signUp.alreadyHaveAnAccount';
+  static const signUp_emailHint = 'signUp.emailHint';
+  static const signUp_passwordHint = 'signUp.passwordHint';
+  static const signUp_repeatPasswordHint = 'signUp.repeatPasswordHint';
+  static const signUp = 'signUp';
+  static const signIn_loginTitle = 'signIn.loginTitle';
+  static const signIn_loginButtonText = 'signIn.loginButtonText';
+  static const signIn_buttonText = 'signIn.buttonText';
+  static const signIn_forgotPassword = 'signIn.forgotPassword';
+  static const signIn_emailHint = 'signIn.emailHint';
+  static const signIn_passwordHint = 'signIn.passwordHint';
+  static const signIn_dontHaveAnAccount = 'signIn.dontHaveAnAccount';
+  static const signIn_repeatPasswordEmptyError = 'signIn.repeatPasswordEmptyError';
+  static const signIn_unmatchedPasswordError = 'signIn.unmatchedPasswordError';
+  static const signIn = 'signIn';
+  static const workspace_create = 'workspace.create';
+  static const workspace_hint = 'workspace.hint';
+  static const workspace_notFoundError = 'workspace.notFoundError';
+  static const workspace = 'workspace';
+  static const shareAction_buttonText = 'shareAction.buttonText';
+  static const shareAction_workInProgress = 'shareAction.workInProgress';
+  static const shareAction_markdown = 'shareAction.markdown';
+  static const shareAction_copyLink = 'shareAction.copyLink';
+  static const shareAction = 'shareAction';
+  static const disclosureAction_rename = 'disclosureAction.rename';
+  static const disclosureAction_delete = 'disclosureAction.delete';
+  static const disclosureAction_duplicate = 'disclosureAction.duplicate';
+  static const disclosureAction = 'disclosureAction';
+  static const blankPageTitle = 'blankPageTitle';
+  static const newPageText = 'newPageText';
+  static const trash_text = 'trash.text';
+  static const trash_restoreAll = 'trash.restoreAll';
+  static const trash_deleteAll = 'trash.deleteAll';
+  static const trash_pageHeader_fileName = 'trash.pageHeader.fileName';
+  static const trash_pageHeader_lastModified = 'trash.pageHeader.lastModified';
+  static const trash_pageHeader_created = 'trash.pageHeader.created';
+  static const trash_pageHeader = 'trash.pageHeader';
+  static const trash = 'trash';
+  static const deletePagePrompt_text = 'deletePagePrompt.text';
+  static const deletePagePrompt_restore = 'deletePagePrompt.restore';
+  static const deletePagePrompt_deletePermanent = 'deletePagePrompt.deletePermanent';
+  static const deletePagePrompt = 'deletePagePrompt';
+  static const dialogCreatePageNameHint = 'dialogCreatePageNameHint';
+  static const questionBubble_whatsNew = 'questionBubble.whatsNew';
+  static const questionBubble_help = 'questionBubble.help';
+  static const questionBubble = 'questionBubble';
+  static const menuAppHeader_addPageTooltip = 'menuAppHeader.addPageTooltip';
+  static const menuAppHeader_defaultNewPageName = 'menuAppHeader.defaultNewPageName';
+  static const menuAppHeader_renameDialog = 'menuAppHeader.renameDialog';
+  static const menuAppHeader = 'menuAppHeader';
+  static const toolbar_undo = 'toolbar.undo';
+  static const toolbar_redo = 'toolbar.redo';
+  static const toolbar_bold = 'toolbar.bold';
+  static const toolbar_italic = 'toolbar.italic';
+  static const toolbar_underline = 'toolbar.underline';
+  static const toolbar_strike = 'toolbar.strike';
+  static const toolbar_numList = 'toolbar.numList';
+  static const toolbar_bulletList = 'toolbar.bulletList';
+  static const toolbar_checkList = 'toolbar.checkList';
+  static const toolbar_inlineCode = 'toolbar.inlineCode';
+  static const toolbar_quote = 'toolbar.quote';
+  static const toolbar = 'toolbar';
+  static const contactsPage_title = 'contactsPage.title';
+  static const contactsPage_whatsHappening = 'contactsPage.whatsHappening';
+  static const contactsPage_addContact = 'contactsPage.addContact';
+  static const contactsPage_editContact = 'contactsPage.editContact';
+  static const contactsPage = 'contactsPage';
+  static const button_OK = 'button.OK';
+  static const button_Cancel = 'button.Cancel';
+  static const button_signIn = 'button.signIn';
+  static const button_signOut = 'button.signOut';
+  static const button_complete = 'button.complete';
+  static const button_save = 'button.save';
+  static const button = 'button';
+  static const label_welcome = 'label.welcome';
+  static const label_firstName = 'label.firstName';
+  static const label_middleName = 'label.middleName';
+  static const label_lastName = 'label.lastName';
+  static const label_stepX = 'label.stepX';
+  static const label = 'label';
+  static const oAuth_err_failedTitle = 'oAuth.err.failedTitle';
+  static const oAuth_err_failedMsg = 'oAuth.err.failedMsg';
+  static const oAuth_err = 'oAuth.err';
+  static const oAuth_google_title = 'oAuth.google.title';
+  static const oAuth_google_instruction1 = 'oAuth.google.instruction1';
+  static const oAuth_google_instruction2 = 'oAuth.google.instruction2';
+  static const oAuth_google_instruction3 = 'oAuth.google.instruction3';
+  static const oAuth_google_instruction4 = 'oAuth.google.instruction4';
+  static const oAuth_google = 'oAuth.google';
+  static const oAuth = 'oAuth';
+
+}

+ 5 - 1
frontend/app_flowy/lib/main.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/presentation/splash_screen.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 
 class FlowyApp implements EntryPoint {
@@ -9,6 +10,9 @@ class FlowyApp implements EntryPoint {
   }
 }
 
-void main() {
+void main() async {
+  WidgetsFlutterBinding.ensureInitialized();
+  await EasyLocalization.ensureInitialized();
+
   System.run(FlowyApp());
 }

+ 12 - 1
frontend/app_flowy/lib/startup/tasks/application_widget.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/startup/startup.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
@@ -17,7 +18,14 @@ class AppWidgetTask extends LaunchTask {
     final widget = context.getIt<EntryPoint>().create();
     final app = ApplicationWidget(child: widget);
     Bloc.observer = ApplicationBlocObserver();
-    runApp(app);
+
+    runApp(
+      EasyLocalization(
+          supportedLocales: const [Locale('en')],
+          path: 'assets/translations',
+          fallbackLocale: const Locale('en'),
+          child: app),
+    );
 
     return Future(() => {});
   }
@@ -45,6 +53,9 @@ class ApplicationWidget extends StatelessWidget {
         builder: overlayManagerBuilder(),
         debugShowCheckedModeBanner: false,
         theme: theme.themeData,
+        localizationsDelegates: context.localizationDelegates,
+        supportedLocales: context.supportedLocales,
+        locale: context.locale,
         navigatorKey: AppGlobals.rootNavKey,
         home: child,
       ),

+ 5 - 3
frontend/app_flowy/lib/user/application/sign_up_bloc.dart

@@ -1,9 +1,11 @@
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:dartz/dartz.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-infra/protobuf.dart' show UserProfile, ErrorCode;
 import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 part 'sign_up_bloc.freezed.dart';
 
@@ -37,7 +39,7 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
     if (password == null) {
       yield state.copyWith(
         isSubmitting: false,
-        passwordError: some("Password can't be empty"),
+        passwordError: some(LocaleKeys.signUp_emptyPasswordError.tr()),
       );
       return;
     }
@@ -45,7 +47,7 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
     if (repeatedPassword == null) {
       yield state.copyWith(
         isSubmitting: false,
-        repeatPasswordError: some("Repeat password can't be empty"),
+        repeatPasswordError: some(LocaleKeys.signUp_repeatPasswordEmptyError.tr()),
       );
       return;
     }
@@ -53,7 +55,7 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
     if (password != repeatedPassword) {
       yield state.copyWith(
         isSubmitting: false,
-        repeatPasswordError: some("Repeat password is not the same as password"),
+        repeatPasswordError: some(LocaleKeys.signUp_unmatchedPasswordError.tr()),
       );
       return;
     }

+ 11 - 9
frontend/app_flowy/lib/user/presentation/sign_in_screen.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/application/sign_in_bloc.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/widgets/background.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
@@ -14,6 +15,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/image.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class SignInScreen extends StatelessWidget {
   final IAuthRouter router;
@@ -58,9 +60,9 @@ class SignInForm extends StatelessWidget {
       alignment: Alignment.center,
       child: AuthFormContainer(
         children: [
-          const FlowyLogoTitle(
-            title: 'Login to Appflowy',
-            logoSize: Size(60, 60),
+          FlowyLogoTitle(
+            title: LocaleKeys.signIn_loginTitle.tr(),
+            logoSize: const Size(60, 60),
           ),
           const VSpace(30),
           const EmailTextField(),
@@ -93,14 +95,14 @@ class SignUpPrompt extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     return Row(
       children: [
-        Text("Dont't have an account", style: TextStyle(color: theme.shader3, fontSize: 12)),
+        Text(LocaleKeys.signIn_dontHaveAnAccount.tr(), style: TextStyle(color: theme.shader3, fontSize: 12)),
         TextButton(
           style: TextButton.styleFrom(
             textStyle: const TextStyle(fontSize: 12),
           ),
           onPressed: () => router.pushSignUpScreen(context),
           child: Text(
-            'Sign Up',
+            LocaleKeys.signUp_buttonText.tr(),
             style: TextStyle(color: theme.main1),
           ),
         ),
@@ -119,7 +121,7 @@ class LoginButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return RoundedTextButton(
-      title: 'Login',
+      title: LocaleKeys.signIn_loginButtonText.tr(),
       height: 48,
       borderRadius: Corners.s10Border,
       color: theme.main1,
@@ -147,7 +149,7 @@ class ForgetPasswordButton extends StatelessWidget {
       ),
       onPressed: () => router.pushForgetPasswordScreen(context),
       child: Text(
-        'Forgot Password?',
+        LocaleKeys.signIn_forgotPassword.tr(),
         style: TextStyle(color: theme.main1),
       ),
     );
@@ -170,7 +172,7 @@ class PasswordTextField extends StatelessWidget {
           style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
           obscureIcon: svg("home/hide"),
           obscureHideIcon: svg("home/show"),
-          hintText: 'Password',
+          hintText: LocaleKeys.signIn_passwordHint.tr(),
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           cursorColor: theme.main1,
@@ -194,7 +196,7 @@ class EmailTextField extends StatelessWidget {
       buildWhen: (previous, current) => previous.emailError != current.emailError,
       builder: (context, state) {
         return RoundedInputField(
-          hintText: 'Email',
+          hintText: LocaleKeys.signIn_emailHint.tr(),
           style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,

+ 11 - 9
frontend/app_flowy/lib/user/presentation/sign_up_screen.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/application/sign_up_bloc.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/widgets/background.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
@@ -13,6 +14,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/image.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class SignUpScreen extends StatelessWidget {
   final IAuthRouter router;
@@ -53,9 +55,9 @@ class SignUpForm extends StatelessWidget {
       alignment: Alignment.center,
       child: AuthFormContainer(
         children: [
-          const FlowyLogoTitle(
-            title: 'Sign Up to Appflowy',
-            logoSize: Size(60, 60),
+          FlowyLogoTitle(
+            title: LocaleKeys.signUp_title.tr(),
+            logoSize: const Size(60, 60),
           ),
           const VSpace(30),
           const EmailTextField(),
@@ -86,13 +88,13 @@ class SignUpPrompt extends StatelessWidget {
     return Row(
       children: [
         Text(
-          "Already have an account?",
+          LocaleKeys.signUp_alreadyHaveAnAccount.tr(),
           style: TextStyle(color: theme.shader3, fontSize: 12),
         ),
         TextButton(
           style: TextButton.styleFrom(textStyle: const TextStyle(fontSize: 12)),
           onPressed: () => Navigator.pop(context),
-          child: Text('Sign In', style: TextStyle(color: theme.main1)),
+          child: Text(LocaleKeys.signIn_buttonText.tr(), style: TextStyle(color: theme.main1)),
         ),
       ],
       mainAxisAlignment: MainAxisAlignment.center,
@@ -109,7 +111,7 @@ class SignUpButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return RoundedTextButton(
-      title: 'Get Started',
+      title: LocaleKeys.signUp_getStartedText.tr(),
       height: 48,
       color: theme.main1,
       onPressed: () {
@@ -135,7 +137,7 @@ class PasswordTextField extends StatelessWidget {
           obscureIcon: svg("home/hide"),
           obscureHideIcon: svg("home/show"),
           style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
-          hintText: "Password",
+          hintText: LocaleKeys.signUp_passwordHint.tr(),
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           cursorColor: theme.main1,
@@ -163,7 +165,7 @@ class RepeatPasswordTextField extends StatelessWidget {
           obscureIcon: svg("home/hide"),
           obscureHideIcon: svg("home/show"),
           style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
-          hintText: "Repeate password",
+          hintText: LocaleKeys.signUp_repeatPasswordHint.tr(),
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           cursorColor: theme.main1,
@@ -187,7 +189,7 @@ class EmailTextField extends StatelessWidget {
       buildWhen: (previous, current) => previous.emailError != current.emailError,
       builder: (context, state) {
         return RoundedInputField(
-          hintText: 'Email',
+          hintText: LocaleKeys.signUp_emailHint.tr(),
           style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,

+ 13 - 11
frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/widgets/background.dart';
 import 'package:app_flowy/workspace/domain/i_user.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/uuid.dart';
@@ -15,6 +16,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:url_launcher/url_launcher.dart';
 import 'package:dartz/dartz.dart' as dartz;
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class SkipLogInScreen extends StatefulWidget {
   final IAuthRouter router;
@@ -50,9 +52,9 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
     return Column(
       mainAxisAlignment: MainAxisAlignment.center,
       children: [
-        const FlowyLogoTitle(
-          title: 'Welcome to AppFlowy',
-          logoSize: Size.square(60),
+        FlowyLogoTitle(
+          title: LocaleKeys.welcomeText.tr(),
+          logoSize: const Size.square(60),
         ),
         const VSpace(80),
         GoButton(onPressed: () => _autoRegister(context)),
@@ -61,18 +63,18 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
           children: [
             InkWell(
-              child: const Text(
-                'Star on Github',
-                style: TextStyle(decoration: TextDecoration.underline, color: Colors.blue),
+              child: Text(
+                LocaleKeys.githubStarText.tr(),
+                style: const TextStyle(decoration: TextDecoration.underline, color: Colors.blue),
               ),
               onTap: () {
                 _launchURL('https://github.com/AppFlowy-IO/appflowy');
               },
             ),
             InkWell(
-              child: const Text(
-                'Subscribe to Newsletter',
-                style: TextStyle(decoration: TextDecoration.underline, color: Colors.blue),
+              child: Text(
+                LocaleKeys.subscribeNewsletterText.tr(),
+                style: const TextStyle(decoration: TextDecoration.underline, color: Colors.blue),
               ),
               onTap: () {
                 _launchURL('https://www.appflowy.io/blog');
@@ -96,7 +98,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
     const password = "AppFlowy123@";
     final uid = uuid();
     final userEmail = "[email protected]";
-    final result = await widget.authManager.signUp("Me", password, userEmail);
+    final result = await widget.authManager.signUp(LocaleKeys.defaultUsername.tr(), password, userEmail);
     result.fold(
       (user) {
         WorkspaceEventReadCurWorkspace().send().then((result) {
@@ -136,7 +138,7 @@ class GoButton extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return RoundedTextButton(
-      title: 'Let\'s Go',
+      title: LocaleKeys.letsGoButtonText.tr(),
       height: 50,
       borderRadius: Corners.s10Border,
       color: theme.main1,

+ 4 - 2
frontend/app_flowy/lib/user/presentation/welcome_screen.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
@@ -8,6 +9,7 @@ import 'package:flowy_sdk/protobuf/flowy-workspace-infra/workspace_create.pb.dar
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class WelcomeScreen extends StatelessWidget {
   final UserRepo repo;
@@ -53,11 +55,11 @@ class WelcomeScreen extends StatelessWidget {
       width: 200,
       height: 40,
       child: FlowyTextButton(
-        "Create workspace",
+        LocaleKeys.workspace_create.tr(),
         fontSize: 14,
         hoverColor: theme.bg3,
         onPressed: () {
-          context.read<WelcomeBloc>().add(const WelcomeEvent.createWorkspace("workspace", ""));
+          context.read<WelcomeBloc>().add(WelcomeEvent.createWorkspace(LocaleKeys.workspace_hint.tr(), ""));
         },
       ),
     );

+ 4 - 2
frontend/app_flowy/lib/workspace/domain/edit_action/app_edit.dart

@@ -1,5 +1,7 @@
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flutter/material.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 enum AppDisclosureAction {
   rename,
@@ -10,9 +12,9 @@ extension AppDisclosureExtension on AppDisclosureAction {
   String get name {
     switch (this) {
       case AppDisclosureAction.rename:
-        return 'rename';
+        return LocaleKeys.disclosureAction_rename.tr();
       case AppDisclosureAction.delete:
-        return 'delete';
+        return LocaleKeys.disclosureAction_delete.tr();
     }
   }
 

+ 5 - 3
frontend/app_flowy/lib/workspace/domain/edit_action/view_edit.dart

@@ -1,5 +1,7 @@
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flutter/material.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 enum ViewDisclosureAction {
   rename,
@@ -11,11 +13,11 @@ extension ViewDisclosureExtension on ViewDisclosureAction {
   String get name {
     switch (this) {
       case ViewDisclosureAction.rename:
-        return 'Rename';
+        return LocaleKeys.disclosureAction_rename.tr();
       case ViewDisclosureAction.delete:
-        return 'Delete';
+        return LocaleKeys.disclosureAction_delete.tr();
       case ViewDisclosureAction.duplicate:
-        return 'Duplicate';
+        return LocaleKeys.disclosureAction_duplicate.tr();
     }
   }
 

+ 3 - 1
frontend/app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 import 'dart:typed_data';
 
 import 'package:dartz/dartz.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_log/flowy_log.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-dart-notify/subject.pb.dart';
@@ -13,6 +14,7 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
+import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/workspace/domain/i_workspace.dart';
 
 import 'helper.dart';
@@ -41,7 +43,7 @@ class WorkspaceRepo {
           assert(workspaces.items.length == 1);
 
           if (workspaces.items.isEmpty) {
-            return right(WorkspaceError.create()..msg = "Workspace not found");
+            return right(WorkspaceError.create()..msg = LocaleKeys.workspace_notFoundError.tr());
           } else {
             return left(workspaces.items[0]);
           }

+ 3 - 1
frontend/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart

@@ -1,6 +1,8 @@
 import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class BlankStackContext extends HomeStackContext {
   final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false);
@@ -9,7 +11,7 @@ class BlankStackContext extends HomeStackContext {
   String get identifier => "1";
 
   @override
-  Widget get leftBarItem => const FlowyText.medium('Blank page', fontSize: 12);
+  Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
 
   @override
   Widget? get rightBarItem => null;

+ 6 - 4
frontend/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart

@@ -6,6 +6,7 @@ import 'package:app_flowy/workspace/domain/view_ext.dart';
 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/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -18,6 +19,7 @@ import 'package:flutter/material.dart';
 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 'doc_page.dart';
 
@@ -170,7 +172,7 @@ class DocShareButton extends StatelessWidget {
         child: BlocBuilder<DocShareBloc, DocShareState>(
           builder: (context, state) {
             return RoundedTextButton(
-              title: 'Share',
+              title: 'shareAction.buttonText'.tr(),
               height: 30,
               width: buttonWidth,
               fontSize: 12,
@@ -222,7 +224,7 @@ class DocShareButton extends StatelessWidget {
   }
 
   void showWorkInProgressDialog(BuildContext context) {
-    const FlowyAlertDialog(title: "Work in progress").show(context);
+    FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context);
   }
 }
 
@@ -279,9 +281,9 @@ extension QuestionBubbleExtension on ShareAction {
   String get name {
     switch (this) {
       case ShareAction.markdown:
-        return "Markdown";
+        return LocaleKeys.shareAction_markdown.tr();
       case ShareAction.copyLink:
-        return "Copy Link";
+        return LocaleKeys.shareAction_copyLink.tr();
     }
   }
 }

+ 6 - 3
frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/banner.dart

@@ -1,3 +1,4 @@
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -5,6 +6,7 @@ import 'package:flowy_infra_ui/widget/buttons/base_styled_button.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class DocBanner extends StatelessWidget {
   final void Function() onRestore;
@@ -22,7 +24,7 @@ class DocBanner extends StatelessWidget {
         color: theme.main1,
         child: Row(
           children: [
-            const FlowyText.medium('This page is in Trash', color: Colors.white),
+            FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(), color: Colors.white),
             const HSpace(20),
             BaseStyledButton(
                 minWidth: 160,
@@ -33,7 +35,7 @@ class DocBanner extends StatelessWidget {
                 downColor: theme.main1,
                 outlineColor: Colors.white,
                 borderRadius: Corners.s8Border,
-                child: const FlowyText.medium('Restore page', color: Colors.white, fontSize: 14),
+                child: FlowyText.medium(LocaleKeys.deletePagePrompt_restore.tr(), color: Colors.white, fontSize: 14),
                 onPressed: onRestore),
             const HSpace(20),
             BaseStyledButton(
@@ -45,7 +47,8 @@ class DocBanner extends StatelessWidget {
                 downColor: theme.main1,
                 outlineColor: Colors.white,
                 borderRadius: Corners.s8Border,
-                child: const FlowyText.medium('Delete permanently', color: Colors.white, fontSize: 14),
+                child: FlowyText.medium(LocaleKeys.deletePagePrompt_deletePermanent.tr(),
+                    color: Colors.white, fontSize: 14),
                 onPressed: onDelete),
           ],
           crossAxisAlignment: CrossAxisAlignment.center,

+ 13 - 11
frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 import 'dart:math';
 
 import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/history_button.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter_quill/flutter_quill.dart';
 import 'package:flutter/material.dart';
 import 'package:styled_widget/styled_widget.dart';
@@ -11,6 +12,7 @@ import 'header_button.dart';
 import 'link_button.dart';
 import 'toggle_button.dart';
 import 'toolbar_icon_button.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
   final List<Widget> children;
@@ -56,42 +58,42 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
           iconSize: toolbarIconSize,
           controller: controller,
           undo: true,
-          tooltipText: 'Undo',
+          tooltipText: LocaleKeys.toolbar_undo.tr(),
         ),
         FlowyHistoryButton(
           icon: Icons.redo_outlined,
           iconSize: toolbarIconSize,
           controller: controller,
           undo: false,
-          tooltipText: 'Redo',
+          tooltipText: LocaleKeys.toolbar_redo.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.bold,
           normalIcon: 'editor/bold',
           iconSize: toolbarIconSize,
           controller: controller,
-          tooltipText: 'Bold',
+          tooltipText: LocaleKeys.toolbar_bold.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.italic,
           normalIcon: 'editor/italic',
           iconSize: toolbarIconSize,
           controller: controller,
-          tooltipText: 'Italic',
+          tooltipText: LocaleKeys.toolbar_italic.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.underline,
           normalIcon: 'editor/underline',
           iconSize: toolbarIconSize,
           controller: controller,
-          tooltipText: 'Underline',
+          tooltipText: LocaleKeys.toolbar_underline.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.strikeThrough,
           normalIcon: 'editor/strikethrough',
           iconSize: toolbarIconSize,
           controller: controller,
-          tooltipText: 'Strikethrough',
+          tooltipText: LocaleKeys.toolbar_strike.tr(),
         ),
         FlowyColorButton(
           icon: Icons.format_color_fill,
@@ -116,34 +118,34 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
           controller: controller,
           normalIcon: 'editor/numbers',
           iconSize: toolbarIconSize,
-          tooltipText: 'Numbered List',
+          tooltipText: LocaleKeys.toolbar_numList.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.ul,
           controller: controller,
           normalIcon: 'editor/bullet_list',
           iconSize: toolbarIconSize,
-          tooltipText: 'Bulleted List',
+          tooltipText: LocaleKeys.toolbar_bulletList.tr(),
         ),
         FlowyCheckListButton(
           attribute: Attribute.unchecked,
           controller: controller,
           iconSize: toolbarIconSize,
-          tooltipText: 'Check List',
+          tooltipText: LocaleKeys.toolbar_checkList.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.inlineCode,
           controller: controller,
           normalIcon: 'editor/inline_block',
           iconSize: toolbarIconSize,
-          tooltipText: 'Inline Code',
+          tooltipText: LocaleKeys.toolbar_inlineCode.tr(),
         ),
         FlowyToggleStyleButton(
           attribute: Attribute.blockQuote,
           controller: controller,
           normalIcon: 'editor/quote',
           iconSize: toolbarIconSize,
-          tooltipText: 'Quote Block',
+          tooltipText: LocaleKeys.toolbar_quote.tr(),
         ),
         FlowyLinkStyleButton(
           controller: controller,

+ 6 - 4
frontend/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart

@@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
 import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
 import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/sizes.dart';
 import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/trash_cell.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
@@ -15,6 +16,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 import 'widget/trash_header.dart';
 
@@ -25,7 +27,7 @@ class TrashStackContext extends HomeStackContext {
   String get identifier => "TrashStackContext";
 
   @override
-  Widget get leftBarItem => const FlowyText.medium('Trash', fontSize: 12);
+  Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12);
 
   @override
   Widget? get rightBarItem => null;
@@ -117,12 +119,12 @@ class _TrashStackPageState extends State<TrashStackPage> {
       height: 36,
       child: Row(
         children: [
-          const FlowyText.semibold('Trash'),
+          FlowyText.semibold(LocaleKeys.trash_text.tr()),
           const Spacer(),
           SizedBox.fromSize(
             size: const Size(102, 30),
             child: FlowyButton(
-              text: const FlowyText.medium('Restore all', fontSize: 12),
+              text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(), fontSize: 12),
               icon: svg('editor/restore'),
               hoverColor: theme.hover,
               onTap: () => context.read<TrashBloc>().add(const TrashEvent.restoreAll()),
@@ -132,7 +134,7 @@ class _TrashStackPageState extends State<TrashStackPage> {
           SizedBox.fromSize(
             size: const Size(102, 30),
             child: FlowyButton(
-              text: const FlowyText.medium('Delete all', fontSize: 12),
+              text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(), fontSize: 12),
               icon: svg('editor/delete'),
               hoverColor: theme.hover,
               onTap: () => context.read<TrashBloc>().add(const TrashEvent.deleteAll()),

+ 5 - 3
frontend/app_flowy/lib/workspace/presentation/stack_page/trash/widget/trash_header.dart

@@ -1,7 +1,9 @@
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 import 'sizes.dart';
 
@@ -34,9 +36,9 @@ class TrashHeaderItem {
 
 class TrashHeader extends StatelessWidget {
   final List<TrashHeaderItem> items = [
-    TrashHeaderItem(title: 'File name', width: TrashSizes.fileNameWidth),
-    TrashHeaderItem(title: 'Last modified', width: TrashSizes.lashModifyWidth),
-    TrashHeaderItem(title: 'Created', width: TrashSizes.createTimeWidth),
+    TrashHeaderItem(title: LocaleKeys.trash_pageHeader_fileName.tr(), width: TrashSizes.fileNameWidth),
+    TrashHeaderItem(title: LocaleKeys.trash_pageHeader_lastModified.tr(), width: TrashSizes.lashModifyWidth),
+    TrashHeaderItem(title: LocaleKeys.trash_pageHeader_created.tr(), width: TrashSizes.createTimeWidth),
   ];
 
   TrashHeader({Key? key}) : super(key: key);

+ 5 - 4
frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart

@@ -1,4 +1,4 @@
-import 'package:flowy_infra/strings.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/text_style.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -13,6 +13,7 @@ import 'package:flowy_infra_ui/style_widget/text_input.dart';
 import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
 import 'package:textstyle_extensions/textstyle_extensions.dart';
 export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class TextFieldDialog extends StatefulWidget {
   final String value;
@@ -53,7 +54,7 @@ class _CreateTextFieldDialog extends State<TextFieldDialog> {
             VSpace(Insets.sm * 1.5),
           ],
           FlowyFormTextInput(
-            hintText: "Page name",
+            hintText: LocaleKeys.dialogCreatePageNameHint.tr(),
             initialValue: widget.value,
             textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
             autoFocus: true,
@@ -196,7 +197,7 @@ class OkCancelButton extends StatelessWidget {
         children: <Widget>[
           if (onCancelPressed != null)
             SecondaryTextButton(
-              cancelTitle ?? S.BTN_CANCEL,
+              cancelTitle ?? LocaleKeys.button_Cancel.tr(),
               onPressed: () {
                 onCancelPressed!();
                 AppGlobals.nav.pop();
@@ -206,7 +207,7 @@ class OkCancelButton extends StatelessWidget {
           HSpace(Insets.m),
           if (onOkPressed != null)
             PrimaryTextButton(
-              okTitle ?? S.BTN_OK,
+              okTitle ?? LocaleKeys.button_OK.tr(),
               onPressed: () {
                 onOkPressed!();
                 AppGlobals.nav.pop();

+ 5 - 7
frontend/app_flowy/lib/workspace/presentation/widgets/edit_pannel/edit_pannel.dart

@@ -3,19 +3,17 @@ import 'package:app_flowy/workspace/domain/edit_context.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
 import 'package:dartz/dartz.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/bar_title.dart';
 import 'package:flowy_infra_ui/style_widget/close_button.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class EditPannel extends StatelessWidget {
   late final EditPannelContext editContext;
   final VoidCallback onEndEdit;
-  EditPannel(
-      {Key? key,
-      required Option<EditPannelContext> context,
-      required this.onEndEdit})
-      : super(key: key) {
+  EditPannel({Key? key, required Option<EditPannelContext> context, required this.onEndEdit}) : super(key: key) {
     editContext = context.fold(() => const BlankEditPannelContext(), (c) => c);
   }
 
@@ -54,8 +52,8 @@ class EditPannelTopBar extends StatelessWidget {
         padding: const EdgeInsets.all(8.0),
         child: Row(
           children: [
-            const FlowyBarTitle(
-              title: 'Title',
+            FlowyBarTitle(
+              title: LocaleKeys.title.tr(),
             ),
             const Spacer(),
             FlowyCloseButton(onPressed: onClose),

+ 5 - 3
frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
@@ -10,6 +11,7 @@ import 'package:dartz/dartz.dart' as dartz;
 import 'package:styled_widget/styled_widget.dart';
 import 'package:package_info_plus/package_info_plus.dart';
 import 'package:url_launcher/url_launcher.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class QuestionBubble extends StatelessWidget {
   const QuestionBubble({Key? key}) : super(key: key);
@@ -22,7 +24,7 @@ class QuestionBubble extends StatelessWidget {
       height: 30,
       child: FlowyTextButton(
         '?',
-        tooltip: 'Help and Support',
+        tooltip: LocaleKeys.questionBubble_help.tr(),
         fontSize: 12,
         fontWeight: FontWeight.w600,
         fillColor: theme.selector,
@@ -163,9 +165,9 @@ extension QuestionBubbleExtension on BubbleAction {
   String get name {
     switch (this) {
       case BubbleAction.whatsNews:
-        return "What's new?";
+        return LocaleKeys.questionBubble_whatsNew.tr();
       case BubbleAction.help:
-        return "Help & Support";
+        return LocaleKeys.questionBubble_help.tr();
     }
   }
 

+ 4 - 2
frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/create_button.dart

@@ -1,11 +1,13 @@
 import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flutter/material.dart';
 import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
 import 'package:flowy_infra_ui/style_widget/extension.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 // ignore: implementation_imports
 
 class NewAppButton extends StatelessWidget {
@@ -15,7 +17,7 @@ class NewAppButton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final child = FlowyTextButton(
-      'New page',
+      LocaleKeys.newPageText.tr(),
       fontSize: 12,
       onPressed: () async => await _showCreateAppDialog(context),
       heading: svgWithSize("home/new_app", const Size(16, 16)),
@@ -30,7 +32,7 @@ class NewAppButton extends StatelessWidget {
 
   Future<void> _showCreateAppDialog(BuildContext context) async {
     return TextFieldDialog(
-      title: 'New page',
+      title: LocaleKeys.newPageText.tr(),
       value: "",
       confirm: (newValue) {
         if (newValue.isNotEmpty && press != null) {

+ 7 - 3
frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/workspace/domain/edit_action/app_edit.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:expandable/expandable.dart';
 import 'package:flowy_infra/flowy_icon_data_icons.dart';
 import 'package:flowy_infra/theme.dart';
@@ -12,6 +13,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:dartz/dartz.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 import '../menu_app.dart';
 import 'add_button.dart';
@@ -99,10 +101,12 @@ class MenuAppHeader extends StatelessWidget {
 
   Widget _renderAddButton(BuildContext context) {
     return Tooltip(
-      message: "Quickly add a page inside",
+      message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
       child: AddButton(
         onSelected: (viewType) {
-          context.read<AppBloc>().add(AppEvent.createView("Untitled", "", viewType));
+          context
+              .read<AppBloc>()
+              .add(AppEvent.createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr(), "", viewType));
         },
       ).padding(right: MenuAppSizes.headerPadding),
     );
@@ -113,7 +117,7 @@ class MenuAppHeader extends StatelessWidget {
       switch (action) {
         case AppDisclosureAction.rename:
           TextFieldDialog(
-            title: 'Rename',
+            title: LocaleKeys.menuAppHeader_renameDialog.tr(),
             value: context.read<AppBloc>().state.app.name,
             confirm: (newValue) {
               context.read<AppBloc>().add(AppEvent.rename(newValue));

+ 3 - 1
frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart

@@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/view/view_bloc.dart';
 import 'package:app_flowy/workspace/domain/edit_action/view_edit.dart';
 import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:dartz/dartz.dart' as dartz;
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -14,6 +15,7 @@ import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:app_flowy/workspace/domain/image.dart';
 import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 import 'disclosure_action.dart';
 
@@ -85,7 +87,7 @@ class ViewSectionItem extends StatelessWidget {
       switch (action) {
         case ViewDisclosureAction.rename:
           TextFieldDialog(
-            title: 'Rename',
+            title: LocaleKeys.disclosureAction_rename.tr(),
             value: context.read<ViewBloc>().state.view.name,
             confirm: (newValue) {
               context.read<ViewBloc>().add(ViewEvent.rename(newValue));

+ 3 - 1
frontend/app_flowy/lib/workspace/presentation/widgets/menu/widget/menu_trash.dart

@@ -2,12 +2,14 @@ import 'package:app_flowy/startup/startup.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_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:provider/provider.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class MenuTrash extends StatelessWidget {
   const MenuTrash({Key? key}) : super(key: key);
@@ -30,7 +32,7 @@ class MenuTrash extends StatelessWidget {
     return Row(children: [
       SizedBox(width: 16, height: 16, child: svg("home/trash")),
       const HSpace(6),
-      const FlowyText.medium('Trash', fontSize: 12),
+      FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12),
     ]);
   }
 }

+ 76 - 1
frontend/app_flowy/pubspec.lock

@@ -267,6 +267,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.4.1"
+  easy_localization:
+    dependency: "direct main"
+    description:
+      name: easy_localization
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.0"
+  easy_logger:
+    dependency: transitive
+    description:
+      name: easy_logger
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.0.2"
   equatable:
     dependency: "direct main"
     description:
@@ -405,6 +419,11 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.4"
+  flutter_localizations:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
   flutter_plugin_android_lifecycle:
     dependency: transitive
     description:
@@ -544,7 +563,7 @@ packages:
     source: hosted
     version: "2.4.1"
   intl:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: intl
       url: "https://pub.dartlang.org"
@@ -851,6 +870,62 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.0.1+1"
+  shared_preferences:
+    dependency: transitive
+    description:
+      name: shared_preferences
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.9"
+  shared_preferences_android:
+    dependency: transitive
+    description:
+      name: shared_preferences_android
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.9"
+  shared_preferences_ios:
+    dependency: transitive
+    description:
+      name: shared_preferences_ios
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.8"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
+  shared_preferences_macos:
+    dependency: transitive
+    description:
+      name: shared_preferences_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.2"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.2"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
   shelf:
     dependency: transitive
     description:

+ 7 - 0
frontend/app_flowy/pubspec.yaml

@@ -29,6 +29,8 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
+  flutter_localizations:
+      sdk: flutter
   flowy_sdk:
     path: packages/flowy_sdk
   flowy_infra_ui:
@@ -43,6 +45,7 @@ dependencies:
       ref: develop
   
   #  third party packages
+  intl: ^0.17.0
   time: '^2.0.0'
   equatable: '^2.0.3'
   freezed_annotation:
@@ -65,6 +68,7 @@ dependencies:
   # file_picker: ^4.2.1
   clipboard: ^0.1.3
   connectivity_plus: ^2.1.0
+  easy_localization: ^3.0.0
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
@@ -89,6 +93,8 @@ dev_dependencies:
 
 # The following section is specific to Flutter.
 flutter:
+  # Automatic code generation for l10n and i18n
+  generate: true
 
   # The following line ensures that the Material Icons font is
   # included with your application, so that you can use the icons in
@@ -105,6 +111,7 @@ flutter:
     - assets/images/
     - assets/images/home/
     - assets/images/editor/
+    - assets/translations/
   #   - images/a_dot_ham.jpeg
 
   # An image asset can refer to one or more resolution-specific "variants", see