浏览代码

feat: support i18n in typescript (#2948)

Kilu.He 1 年之前
父节点
当前提交
0dae8cf2f9
共有 100 个文件被更改,包括 1181 次插入690 次删除
  1. 2 1
      frontend/appflowy_tauri/.gitignore
  2. 9 6
      frontend/appflowy_tauri/package.json
  3. 177 20
      frontend/appflowy_tauri/pnpm-lock.yaml
  4. 53 0
      frontend/appflowy_tauri/scripts/i18n/index.cjs
  5. 8 0
      frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts
  6. 7 0
      frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts
  7. 1 3
      frontend/appflowy_tauri/src/appflowy_app/App.tsx
  8. 10 0
      frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts
  9. 5 5
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx
  10. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx
  11. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListOption.tsx
  12. 5 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx
  13. 3 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateFormatPopup.tsx
  14. 11 5
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTypeOptions.tsx
  15. 3 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormatPopup.tsx
  16. 6 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/TimeFormatPopup.tsx
  17. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx
  18. 6 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx
  19. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx
  20. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOption.tsx
  21. 2 6
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptions.tsx
  22. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptionsPopup.tsx
  23. 5 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx
  24. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/SelectedOption.tsx
  25. 18 8
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx
  26. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupSelect.tsx
  27. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupWindow.tsx
  28. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/SearchInput.tsx
  29. 9 9
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/getColor.ts
  30. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorCheckSvg.tsx
  31. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorUncheckSvg.tsx
  32. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/FullView.tsx
  33. 5 5
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupBySvg.tsx
  34. 6 3
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx
  35. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardFieldsPopup.tsx
  36. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx
  37. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroupFieldsPopup.tsx
  38. 13 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx
  39. 3 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx
  40. 4 2
      frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx
  41. 2 3
      frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/BlockSlashMenu.tsx
  42. 1 2
      frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx
  43. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx
  44. 6 3
      frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/SelectLanguage.tsx
  45. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx
  46. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/DocumentTitle/index.tsx
  47. 6 3
      frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/index.tsx
  48. 10 11
      frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/EditImage.tsx
  49. 6 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx
  50. 4 2
      frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx
  51. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx
  52. 6 3
      frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx
  53. 5 2
      frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx
  54. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx
  55. 16 16
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx
  56. 0 16
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx
  57. 5 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx
  58. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx
  59. 9 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx
  60. 18 11
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx
  61. 1 0
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx
  62. 14 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx
  63. 10 6
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx
  64. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx
  65. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx
  66. 17 14
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx
  67. 5 3
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx
  68. 5 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx
  69. 16 0
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx
  70. 38 14
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx
  71. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx
  72. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.tsx
  73. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeaderItem.tsx
  74. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRow.tsx
  75. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/FooterPanel.tsx
  76. 8 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx
  77. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx
  78. 0 16
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx
  79. 45 0
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx
  80. 0 23
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx
  81. 7 14
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts
  82. 56 13
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx
  83. 84 0
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx
  84. 23 54
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts
  85. 92 91
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx
  86. 0 57
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx
  87. 10 18
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
  88. 67 0
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx
  89. 0 57
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx
  90. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx
  91. 52 0
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx
  92. 0 47
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx
  93. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx
  94. 8 5
      frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/AppearanceSetting.tsx
  95. 69 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/LanguageSetting.tsx
  96. 8 5
      frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/Menu.tsx
  97. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/SettingPanel.tsx
  98. 10 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/index.tsx
  99. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx
  100. 12 12
      frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx

+ 2 - 1
frontend/appflowy_tauri/.gitignore

@@ -24,4 +24,5 @@ dist-ssr
 *.sw?
 
 **/src/services/backend/models/
-**/src/services/backend/events/
+**/src/services/backend/events/
+**/src/appflowy_app/i18n/translations/

+ 9 - 6
frontend/appflowy_tauri/package.json

@@ -5,14 +5,16 @@
   "type": "module",
   "scripts": {
     "dev": "vite",
-    "build": "tsc && vite build",
+    "build": "pnpm sync:i18n && tsc && vite build",
     "preview": "vite preview",
     "format": "prettier --write .",
     "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
-    "test:errors": "tsc --noEmit && eslint --quiet --ext .js,.ts,.tsx .",
-    "test:prettier": "yarn prettier --list-different src",
+    "test:errors": "pnpm sync:i18n && tsc --noEmit && eslint --quiet --ext .js,.ts,.tsx .",
+    "test:prettier": "pnpm prettier --list-different src",
     "tauri:clean": "cargo make --cwd .. tauri_clean",
-    "tauri:dev": "tauri dev"
+    "tauri:dev": "pnpm sync:i18n && tauri dev",
+    "sync:i18n": "node scripts/i18n/index.cjs",
+    "css:variables": "node style-dictionary/config.cjs"
   },
   "dependencies": {
     "@emoji-mart/data": "^1.1.2",
@@ -25,13 +27,14 @@
     "@slate-yjs/core": "^1.0.0",
     "@tanstack/react-virtual": "3.0.0-beta.54",
     "@tauri-apps/api": "^1.2.0",
-    "dayjs": "^1.11.7",
+    "dayjs": "^1.11.9",
     "emoji-mart": "^5.5.2",
     "emoji-regex": "^10.2.1",
     "events": "^3.3.0",
     "google-protobuf": "^3.21.2",
     "i18next": "^22.4.10",
     "i18next-browser-languagedetector": "^7.0.1",
+    "i18next-resources-to-backend": "^1.1.4",
     "is-hotkey": "^0.2.0",
     "jest": "^29.5.0",
     "katex": "^0.16.7",
@@ -56,7 +59,6 @@
     "slate-react": "^0.94.2",
     "ts-results": "^3.3.0",
     "utf8": "^3.0.0",
-    "y-indexeddb": "^9.0.9",
     "yjs": "^13.5.51"
   },
   "devDependencies": {
@@ -83,6 +85,7 @@
     "postcss": "^8.4.21",
     "prettier": "2.8.4",
     "prettier-plugin-tailwindcss": "^0.2.2",
+    "style-dictionary": "^3.8.0",
     "tailwindcss": "^3.2.7",
     "typescript": "^4.6.4",
     "uuid": "^9.0.0",

+ 177 - 20
frontend/appflowy_tauri/pnpm-lock.yaml

@@ -32,8 +32,8 @@ dependencies:
     specifier: ^1.2.0
     version: 1.3.0
   dayjs:
-    specifier: ^1.11.7
-    version: 1.11.7
+    specifier: ^1.11.9
+    version: 1.11.9
   emoji-mart:
     specifier: ^5.5.2
     version: 5.5.2
@@ -52,6 +52,9 @@ dependencies:
   i18next-browser-languagedetector:
     specifier: ^7.0.1
     version: 7.0.1
+  i18next-resources-to-backend:
+    specifier: ^1.1.4
+    version: 1.1.4
   is-hotkey:
     specifier: ^0.2.0
     version: 0.2.0
@@ -124,9 +127,6 @@ dependencies:
   utf8:
     specifier: ^3.0.0
     version: 3.0.0
-  y-indexeddb:
-    specifier: ^9.0.9
-    version: 9.0.11([email protected])
   yjs:
     specifier: ^13.5.51
     version: 13.6.1
@@ -201,6 +201,9 @@ devDependencies:
   prettier-plugin-tailwindcss:
     specifier: ^0.2.2
     version: 0.2.8([email protected])
+  style-dictionary:
+    specifier: ^3.8.0
+    version: 3.8.0
   tailwindcss:
     specifier: ^3.2.7
     version: 3.3.2
@@ -2193,6 +2196,13 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
+  /[email protected]:
+    resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+    dependencies:
+      pascal-case: 3.1.2
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
     engines: {node: '>= 6'}
@@ -2211,6 +2221,14 @@ packages:
   /[email protected]:
     resolution: {integrity: sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==}
 
+  /[email protected]:
+    resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.5.0
+      upper-case-first: 2.0.2
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
     engines: {node: '>=4'}
@@ -2226,6 +2244,23 @@ packages:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
+  /[email protected]:
+    resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
+    dependencies:
+      camel-case: 4.1.2
+      capital-case: 1.0.4
+      constant-case: 3.0.4
+      dot-case: 3.0.4
+      header-case: 2.0.4
+      no-case: 3.0.4
+      param-case: 3.0.4
+      pascal-case: 3.1.2
+      path-case: 3.0.4
+      sentence-case: 3.0.4
+      snake-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
     engines: {node: '>=10'}
@@ -2308,7 +2343,6 @@ packages:
   /[email protected]:
     resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
     engines: {node: '>= 12'}
-    dev: false
 
   /[email protected]:
     resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
@@ -2317,6 +2351,14 @@ packages:
   /[email protected]:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
+  /[email protected]:
+    resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.5.0
+      upper-case: 2.0.2
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
 
@@ -2358,8 +2400,8 @@ packages:
   /[email protected]:
     resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
 
-  /[email protected].7:
-    resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
+  /[email protected].9:
+    resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
     dev: false
 
   /[email protected]:
@@ -2455,6 +2497,13 @@ packages:
       csstype: 3.1.2
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==}
 
@@ -2884,6 +2933,15 @@ packages:
     resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      graceful-fs: 4.2.11
+      jsonfile: 6.1.0
+      universalify: 2.0.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
 
@@ -3029,7 +3087,6 @@ packages:
 
   /[email protected]:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
-    dev: false
 
   /[email protected]:
     resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@@ -3072,6 +3129,13 @@ packages:
     dependencies:
       function-bind: 1.1.1
 
+  /[email protected]:
+    resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
+    dependencies:
+      capital-case: 1.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
     dependencies:
@@ -3099,6 +3163,12 @@ packages:
       '@babel/runtime': 7.21.5
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-hMyr9AOmIea17AOaVe1srNxK/l3mbk81P7Uf3fdcjlw3ehZy3UNTd0OP3EEi6yu4J02kf9jzhCcjokz6AFlEOg==}
+    dependencies:
+      '@babel/runtime': 7.21.5
+    dev: false
+
   /[email protected]:
     resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==}
     dependencies:
@@ -3825,6 +3895,18 @@ packages:
     engines: {node: '>=6'}
     hasBin: true
 
+  /[email protected]:
+    resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
+    dev: true
+
+  /[email protected]:
+    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+    dependencies:
+      universalify: 2.0.0
+    optionalDependencies:
+      graceful-fs: 4.2.11
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==}
     engines: {node: '>=4.0'}
@@ -3904,7 +3986,6 @@ packages:
 
   /[email protected]:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
-    dev: false
 
   /[email protected]:
     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
@@ -3912,6 +3993,12 @@ packages:
     dependencies:
       js-tokens: 4.0.0
 
+  /[email protected]:
+    resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+    dependencies:
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
     dependencies:
@@ -4003,6 +4090,13 @@ packages:
   /[email protected]:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
+  /[email protected]:
+    resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+    dependencies:
+      lower-case: 2.0.2
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
     dev: false
@@ -4151,6 +4245,13 @@ packages:
     engines: {node: '>=6'}
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+    dependencies:
+      dot-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==}
 
@@ -4170,6 +4271,20 @@ packages:
       lines-and-columns: 1.2.4
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
+  /[email protected]:
+    resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
+    dependencies:
+      dot-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
     engines: {node: '>=8'}
@@ -4799,6 +4914,14 @@ packages:
     dependencies:
       lru-cache: 6.0.0
 
+  /[email protected]:
+    resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.5.0
+      upper-case-first: 2.0.2
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
@@ -4858,6 +4981,13 @@ packages:
       tiny-warning: 1.0.3
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+    dependencies:
+      dot-case: 3.0.4
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
     engines: {node: '>=0.10.0'}
@@ -4966,6 +5096,22 @@ packages:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
 
+  /[email protected]:
+    resolution: {integrity: sha512-wHlB/f5eO3mDcYv6WtOz6gvQC477jBKrwuIXe+PtHskTCBsJdAOvL8hCquczJxDui2TnwpeNE+2msK91JJomZg==}
+    engines: {node: '>=12.0.0'}
+    hasBin: true
+    dependencies:
+      chalk: 4.1.2
+      change-case: 4.1.2
+      commander: 8.3.0
+      fs-extra: 10.1.0
+      glob: 7.2.3
+      json5: 2.2.3
+      jsonc-parser: 3.2.0
+      lodash: 4.17.21
+      tinycolor2: 1.6.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
     dev: false
@@ -5077,6 +5223,10 @@ packages:
     resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
     dev: false
 
+  /[email protected]:
+    resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
     dev: false
@@ -5105,7 +5255,6 @@ packages:
 
   /[email protected]:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
-    dev: false
 
   /[email protected]([email protected]):
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@@ -5161,6 +5310,11 @@ packages:
       which-boxed-primitive: 1.0.2
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
+    engines: {node: '>= 10.0.0'}
+    dev: true
+
   /[email protected]([email protected]):
     resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==}
     hasBin: true
@@ -5171,6 +5325,18 @@ packages:
       escalade: 3.1.1
       picocolors: 1.0.0
 
+  /[email protected]:
+    resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
+    dependencies:
+      tslib: 2.5.0
+    dev: true
+
+  /[email protected]:
+    resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
+    dependencies:
+      tslib: 2.5.0
+    dev: true
+
   /[email protected]:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
     dependencies:
@@ -5313,15 +5479,6 @@ packages:
       signal-exit: 3.0.7
     dev: false
 
-  /[email protected]([email protected]):
-    resolution: {integrity: sha512-HOKQ70qW1h2WJGtOKu9rE8fbX86ExVZedecndMuhwax3yM4DQsQzCTGHt/jvTrFZr/9Ahvd8neD6aZ4dMMjtdg==}
-    peerDependencies:
-      yjs: ^13.0.0
-    dependencies:
-      lib0: 0.2.74
-      yjs: 13.6.1
-    dev: false
-
   /[email protected]:
     resolution: {integrity: sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==}
     dependencies:

+ 53 - 0
frontend/appflowy_tauri/scripts/i18n/index.cjs

@@ -0,0 +1,53 @@
+const languages = [
+    'ar-SA',
+    'ca-ES',
+    'de-DE',
+    'en',
+    'es-VE',
+    'eu-ES',
+    'fr-FR',
+    'hu-HU',
+    'id-ID',
+    'it-IT',
+    'ja-JP',
+    'ko-KR',
+    'pl-PL',
+    'pt-BR',
+    'pt-PT',
+    'ru-RU',
+    'sv',
+    'tr-TR',
+    'zh-CN',
+    'zh-TW',
+];
+
+const fs = require('fs');
+languages.forEach(language => {
+    const json = require(`../../../resources/translations/${language}.json`);
+    const outputJSON = flattenJSON(json);
+    const output = JSON.stringify(outputJSON);
+    const isExistDir = fs.existsSync('./src/appflowy_app/i18n/translations');
+    if (!isExistDir) {
+        fs.mkdirSync('./src/appflowy_app/i18n/translations');
+    }
+    fs.writeFile(`./src/appflowy_app/i18n/translations/${language}.json`, new Uint8Array(Buffer.from(output)), (res) => {
+        if (res) {
+            console.error(res);
+        }
+    })
+})
+function flattenJSON(obj, prefix = '') {
+    let result = {};
+
+    for (let key in obj) {
+        if (typeof obj[key] === 'object' && obj[key] !== null) {
+            const nestedKeys = flattenJSON(obj[key], `${prefix}${key}.`);
+            result = { ...result, ...nestedKeys };
+        } else {
+            result[`${prefix}${key}`] = obj[key];
+        }
+    }
+
+    return result;
+}
+

+ 8 - 0
frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts

@@ -0,0 +1,8 @@
+import resources from './resources';
+
+declare module 'i18next' {
+  interface CustomTypeOptions {
+    defaultNS: 'translation';
+    resources: typeof resources;
+  }
+}

+ 7 - 0
frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts

@@ -0,0 +1,7 @@
+import translation from '$app/i18n/translations/en.json';
+
+const resources = {
+  translation,
+} as const;
+
+export default resources;

+ 1 - 3
frontend/appflowy_tauri/src/appflowy_app/App.tsx

@@ -4,14 +4,12 @@ import { Provider } from 'react-redux';
 import { store } from './stores/store';
 
 import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
-import initializeI18n from './stores/i18n/initializeI18n';
+import '$app/i18n/config';
 
 import { ErrorBoundary } from 'react-error-boundary';
 
 import AppMain from '$app/AppMain';
 
-initializeI18n();
-
 const App = () => {
   return (
     <BrowserRouter>

+ 10 - 0
frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts

@@ -19,10 +19,20 @@ export function useUserSetting() {
   useEffect(() => {
     userSettingController?.getAppearanceSetting().then((res) => {
       if (!res) return;
+      const locale = res.locale;
+      let language = 'en';
+
+      if (locale.language_code && locale.country_code) {
+        language = `${locale.language_code}-${locale.country_code}`;
+      } else if (locale.language_code) {
+        language = locale.language_code;
+      }
+
       dispatch(
         currentUserActions.setUserSetting({
           themeMode: res.theme_mode,
           theme: res.theme as Theme,
+          language: language,
         })
       );
     });

+ 5 - 5
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx

@@ -14,23 +14,23 @@ export const Button = ({
   useEffect(() => {
     switch (size) {
       case 'primary':
-        setCls('w-[340px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill');
+        setCls('w-[340px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill');
         break;
       case 'medium':
-        setCls('w-[170px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill');
+        setCls('w-[170px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill');
         break;
       case 'small':
         setCls(
-          'w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill text-xs hover:bg-content-hover'
+          'w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill text-xs hover:bg-content-hover'
         );
         break;
       case 'medium-transparent':
         setCls(
-          'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-content-default text-content-default transition-colors duration-300 hover:bg-content-hover hover:text-content-onfill'
+          'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-content-default text-content-default transition-colors duration-300 hover:bg-content-blue-50 hover:text-content-on-fill'
         );
         break;
       case 'box-small-transparent':
-        setCls('text-icon-default w-[24px] h-[24px] rounded hover:bg-fill-hover');
+        setCls('text-icon-default w-[24px] h-[24px] rounded hover:bg-fill-list-hover');
         break;
     }
   }, [size]);

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx

@@ -32,7 +32,7 @@ export const ChangeFieldTypePopup = ({
           <button
             onClick={() => onClick(t)}
             key={i}
-            className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-hover'}
+            className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-list-hover'}
           >
             <i className={'h-5 w-5'}>
               <FieldTypeIcon fieldType={t}></FieldTypeIcon>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListOption.tsx

@@ -37,7 +37,7 @@ export const CheckListOption = ({
 
   return (
     <div
-      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'}
       onClick={() =>
         onToggleOptionClick(
           new SelectOptionPB({

+ 5 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx

@@ -69,7 +69,7 @@ export const EditCheckListPopup = ({
       top={top}
     >
       <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
-        <div className={'flex flex-1 items-center gap-2 rounded border border-line-border bg-fill-hover px-2 '}>
+        <div className={'flex flex-1 items-center gap-2 rounded border border-line-divider bg-fill-list-hover px-2 '}>
           <input
             ref={inputRef}
             className={'py-2'}
@@ -78,11 +78,13 @@ export const EditCheckListPopup = ({
             onKeyDown={onKeyDown}
             onBlur={() => onBlur()}
           />
-          <div className={'font-mono text-shade-3'}>{value.length}/30</div>
+          <div className={'text-shade-3 font-mono'}>{value.length}/30</div>
         </div>
         <button
           onClick={() => onDeleteOptionClick()}
-          className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-fill-default hover:bg-fill-hover'}
+          className={
+            'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-fill-default hover:bg-fill-list-hover'
+          }
         >
           <i className={'h-5 w-5'}>
             <TrashSvg></TrashSvg>

+ 3 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateFormatPopup.tsx

@@ -80,7 +80,9 @@ function PopupItem({
   return (
     <button
       onClick={() => changeFormat(format)}
-      className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+      className={
+        'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'
+      }
     >
       {text}
 

+ 11 - 5
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTypeOptions.tsx

@@ -87,20 +87,24 @@ export const DateTypeOptions = ({
 
   return (
     <div className={'flex flex-col'}>
-      <hr className={'-mx-2 my-2 border-shade-6'} />
+      <hr className={'border-shade-6 -mx-2 my-2'} />
       <button
         onClick={_onDateFormatClick}
-        className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-hover'}
+        className={
+          'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-list-hover'
+        }
       >
         <span>{t('grid.field.dateFormat')}</span>
         <i className={'h-5 w-5'}>
           <MoreSvg></MoreSvg>
         </i>
       </button>
-      <hr className={'-mx-2 my-2 border-line-border'} />
+      <hr className={'-mx-2 my-2 border-line-divider'} />
       <button
         onClick={() => toggleIncludeTime()}
-        className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-hover'}
+        className={
+          'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-list-hover'
+        }
       >
         <div className={'flex items-center gap-2'}>
           <span>{t('grid.field.includeTime')}</span>
@@ -112,7 +116,9 @@ export const DateTypeOptions = ({
 
       <button
         onClick={_onTimeFormatClick}
-        className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-hover'}
+        className={
+          'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-list-hover'
+        }
       >
         <span>{t('grid.field.timeFormat')}</span>
         <i className={'h-5 w-5'}>

+ 3 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormatPopup.tsx

@@ -93,7 +93,9 @@ const FormatButton = ({ title, checked, onClick }: { title: string; checked: boo
   return (
     <button
       onClick={() => onClick()}
-      className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+      className={
+        'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'
+      }
     >
       <span className={'block pr-8'}>{title}</span>
       {checked && (

+ 6 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/TimeFormatPopup.tsx

@@ -41,7 +41,9 @@ export const TimeFormatPopup = ({
     <PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
       <button
         onClick={() => changeFormat(TimeFormatPB.TwelveHour)}
-        className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+        className={
+          'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'
+        }
       >
         {t('grid.field.timeFormatTwelveHour')}
 
@@ -53,7 +55,9 @@ export const TimeFormatPopup = ({
       </button>
       <button
         onClick={() => changeFormat(TimeFormatPB.TwentyFourHour)}
-        className={'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+        className={
+          'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'
+        }
       >
         {t('grid.field.timeFormatTwentyFourHour')}
 

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx

@@ -59,7 +59,7 @@ export const EditCellWrapper = ({
             <div
               ref={el}
               onClick={() => onClick()}
-              className={'flex h-5 w-5 rounded text-icon-default hover:bg-fill-hover'}
+              className={'text-icon-default flex h-5 w-5 rounded hover:bg-fill-list-hover'}
             >
               <DragElementSvg></DragElementSvg>
             </div>
@@ -72,7 +72,7 @@ export const EditCellWrapper = ({
             </span>
           </div>
 
-          <div className={'w-full cursor-pointer rounded-lg pl-3 text-sm hover:bg-fill-selector'}>
+          <div className={'w-full cursor-pointer rounded-lg pl-3 text-sm hover:bg-content-blue-50'}>
             {(cellIdentifier.fieldType === FieldType.SingleSelect ||
               cellIdentifier.fieldType === FieldType.MultiSelect) &&
               cellController && (

+ 6 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx

@@ -101,7 +101,7 @@ export const EditFieldPopup = ({
           onChange={(e) => setName(e.target.value)}
           onBlur={() => save()}
           className={
-            'flex-1 rounded border border-line-border px-2 py-2 hover:border-fill-default focus:border-fill-default'
+            'flex-1 rounded border border-line-divider px-2 py-2 hover:border-fill-default focus:border-fill-default'
           }
         />
 
@@ -109,7 +109,7 @@ export const EditFieldPopup = ({
           ref={changeTypeButtonRef}
           onClick={() => onChangeFieldTypeClick()}
           className={
-            'relative flex cursor-pointer items-center justify-between rounded-lg py-2 text-text-title hover:bg-fill-hover'
+            'relative flex cursor-pointer items-center justify-between rounded-lg py-2 text-text-title hover:bg-fill-list-hover'
           }
         >
           <button className={'flex cursor-pointer items-center gap-2 rounded-lg pl-2'}>
@@ -129,10 +129,12 @@ export const EditFieldPopup = ({
 
         {cellIdentifier.fieldType === FieldType.Number && (
           <>
-            <hr className={'-mx-2 border-line-border'} />
+            <hr className={'-mx-2 border-line-divider'} />
             <button
               onClick={onNumberFormatClick}
-              className={'flex w-full cursor-pointer items-center justify-between rounded-lg py-2 hover:bg-fill-hover'}
+              className={
+                'flex w-full cursor-pointer items-center justify-between rounded-lg py-2 hover:bg-fill-list-hover'
+              }
             >
               <span className={'pl-2'}>{t('grid.field.numberFormat')}</span>
               <span className={'pr-2'}>

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx

@@ -206,13 +206,13 @@ export const EditRow = ({
           className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-bg-body `}
         >
           <div onClick={() => onCloseClick()} className={'absolute right-1 top-1'}>
-            <button className={'block h-8 w-8 rounded-lg text-text-title hover:bg-fill-hover'}>
+            <button className={'block h-8 w-8 rounded-lg text-text-title hover:bg-fill-list-hover'}>
               <CloseSvg></CloseSvg>
             </button>
           </div>
 
           <div className={'flex h-full'}>
-            <div className={'flex h-full flex-1 flex-col border-r border-line-border pb-4 pt-6'}>
+            <div className={'flex h-full flex-1 flex-col border-r border-line-divider pb-4 pt-6'}>
               <div className={'pb-4 pl-12'}>
                 <button className={'flex items-center gap-2 p-4'}>
                   <i className={'h-5 w-5'}>
@@ -254,10 +254,10 @@ export const EditRow = ({
                 </Droppable>
               </DragDropContext>
 
-              <div className={'border-t border-line-border px-8 pt-2'}>
+              <div className={'border-t border-line-divider px-8 pt-2'}>
                 <button
                   onClick={() => onNewColumnClick()}
-                  className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-fill-hover'}
+                  className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-fill-list-hover'}
                 >
                   <i className={'h-5 w-5'}>
                     <AddSvg></AddSvg>

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOption.tsx

@@ -53,9 +53,9 @@ export const CellOption = ({
   return (
     <div
       onClick={onToggleOptionClick}
-      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-hover'}
+      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-fill-list-hover'}
     >
-      <div className={`${getBgColor(option.color)} rounded px-2 py-0.5 text-content-onfill`}>{option.title}</div>
+      <div className={`${getBgColor(option.color)} rounded px-2 py-0.5 text-text-title`}>{option.title}</div>
       <div className={'flex items-center'}>
         {checked && (
           <button className={'h-5 w-5 p-1'}>

+ 2 - 6
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptions.tsx

@@ -19,13 +19,9 @@ export const CellOptions = ({
   };
 
   return (
-    <div
-      ref={ref}
-      onClick={onClick}
-      className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs text-content-onfill'}
-    >
+    <div ref={ref} onClick={onClick} className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs'}>
       {data?.select_options?.map((option, index) => (
-        <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
+        <div className={`${getBgColor(option.color)} rounded px-2 py-0.5 text-text-title`} key={index}>
           {option?.name ?? ''}
         </div>
       ))}

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptionsPopup.tsx

@@ -59,7 +59,7 @@ export const CellOptionsPopup = ({
       <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
         <div
           className={
-            'flex flex-1 items-center gap-2 rounded border border-line-border px-2 hover:border-fill-default focus:border-fill-default'
+            'flex flex-1 items-center gap-2 rounded border border-line-divider px-2 hover:border-fill-default focus:border-fill-default'
           }
         >
           <div className={'flex flex-wrap items-center gap-2 text-text-title'}>

+ 5 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx

@@ -86,7 +86,7 @@ export const EditCellOptionPopup = ({
       <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
         <div
           className={
-            'flex flex-1 items-center gap-2 rounded border border-line-border px-2 hover:border-fill-hover focus:border-fill-hover'
+            'flex flex-1 items-center gap-2 rounded border border-line-divider px-2 hover:border-fill-hover focus:border-fill-hover'
           }
         >
           <input
@@ -101,7 +101,9 @@ export const EditCellOptionPopup = ({
         </div>
         <button
           onClick={() => onDeleteOptionClick()}
-          className={'text-main-alert flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+          className={
+            'text-main-alert flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-list-hover'
+          }
         >
           <i className={'h-5 w-5'}>
             <TrashSvg></TrashSvg>
@@ -184,7 +186,7 @@ const ColorItem = ({
 }) => {
   return (
     <div
-      className={'flex cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-fill-hover'}
+      className={'flex cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-fill-list-hover'}
       onClick={() => onClick()}
     >
       <div className={'flex items-center gap-2'}>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/SelectedOption.tsx

@@ -20,7 +20,7 @@ export const SelectedOption = ({
   };
 
   return (
-    <div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5 text-content-onfill`}>
+    <div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5 text-content-on-fill`}>
       <span>{option?.name ?? ''}</span>
       <button onClick={onUnselectOptionClick} className={'h-5 w-5 cursor-pointer'}>
         <CloseSvg></CloseSvg>

+ 18 - 8
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx

@@ -103,7 +103,7 @@ export const PropertiesPanel = ({
       <div
         onClick={() => setShowAddedProperties(!showAddedProperties)}
         className={
-          'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 text-text-title hover:bg-bg-base'
+          'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 text-text-title hover:bg-fill-list-active'
         }
       >
         <div className={'text-sm'}>Added Properties</div>
@@ -118,7 +118,7 @@ export const PropertiesPanel = ({
               key={cellIndex}
               onMouseEnter={() => setHoveredPropertyIndex(cellIndex)}
               className={
-                'flex cursor-pointer items-center justify-between gap-4 rounded-lg px-2 py-1 hover:bg-fill-hover'
+                'flex cursor-pointer items-center justify-between gap-4 rounded-lg px-2 py-1 hover:bg-fill-list-hover'
               }
             >
               <div className={'flex items-center gap-2 text-text-title '}>
@@ -148,7 +148,9 @@ export const PropertiesPanel = ({
       </div>
       <div
         onClick={() => setShowBasicProperties(!showBasicProperties)}
-        className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+        className={
+          'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-list-active'
+        }
       >
         <div className={'text-sm'}>Basic Properties</div>
         <i className={`h-5 w-5 transition-transform duration-500 ${showBasicProperties && 'rotate-180'}`}>
@@ -162,7 +164,7 @@ export const PropertiesPanel = ({
               <button
                 onClick={() => addSelectedFieldType(type)}
                 key={i}
-                className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-hover'}
+                className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-list-hover'}
               >
                 <i className={'h-5 w-5'}>
                   <FieldTypeIcon fieldType={type}></FieldTypeIcon>
@@ -177,7 +179,9 @@ export const PropertiesPanel = ({
       </div>
       <div
         onClick={() => setShowAdvancedProperties(!showAdvancedProperties)}
-        className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+        className={
+          'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-list-active'
+        }
       >
         <div className={'text-sm'}>Advanced Properties</div>
         <i className={`h-5 w-5 transition-transform duration-500 ${showAdvancedProperties && 'rotate-180'}`}>
@@ -187,19 +191,25 @@ export const PropertiesPanel = ({
       <div className={'flex flex-col gap-2 text-xs'}>
         {showAdvancedProperties && (
           <div className={'flex flex-col'}>
-            <button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-hover'}>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-list-hover'}
+            >
               <i className={'h-5 w-5'}>
                 <MultiSelectTypeSvg></MultiSelectTypeSvg>
               </i>
               <span>Last edited time</span>
             </button>
-            <button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-hover'}>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-list-hover'}
+            >
               <i className={'h-5 w-5'}>
                 <DocumentSvg></DocumentSvg>
               </i>
               <span>Document</span>
             </button>
-            <button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-hover'}>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-fill-list-hover'}
+            >
               <i className={'h-5 w-5'}>
                 <SingleSelectTypeSvg></SingleSelectTypeSvg>
               </i>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupSelect.tsx

@@ -39,7 +39,7 @@ export const PopupSelect = ({
         {items.map((item, index) => (
           <button
             key={index}
-            className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+            className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-list-hover'}
             onClick={(e) => handleClick(e, item)}
           >
             <>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupWindow.tsx

@@ -42,7 +42,7 @@ export const PopupWindow = ({
     <div
       ref={ref}
       className={
-        'fixed z-10 rounded-lg bg-bg-base shadow-md transition-opacity duration-300 ' +
+        'fixed z-10 rounded-lg bg-bg-body shadow-md transition-opacity duration-300 ' +
         (adjustedTop === -100 && adjustedLeft === -100 ? 'opacity-0 ' : 'opacity-100 ') +
         (className ?? '')
       }

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/SearchInput.tsx

@@ -5,7 +5,7 @@ export const SearchInput = () => {
   const [active, setActive] = useState(false);
 
   return (
-    <div className={`flex items-center rounded-lg border p-2 ${active ? 'border-fill-default' : 'border-line-border'}`}>
+    <div className={`flex items-center rounded-lg border p-2 ${active ? 'border-fill-default' : 'border-line-divider'}`}>
       <i className='mr-2 h-5 w-5'>
         <SearchSvg />
       </i>

+ 9 - 9
frontend/appflowy_tauri/src/appflowy_app/components/_shared/getColor.ts

@@ -3,23 +3,23 @@ import { SelectOptionColorPB } from '../../../services/backend';
 export const getBgColor = (color: SelectOptionColorPB | undefined): string => {
   switch (color) {
     case SelectOptionColorPB.Purple:
-      return 'bg-tint-1';
+      return 'bg-tint-purple';
     case SelectOptionColorPB.Pink:
-      return 'bg-tint-2';
+      return 'bg-tint-pink';
     case SelectOptionColorPB.LightPink:
-      return 'bg-tint-3';
+      return 'bg-tint-red';
     case SelectOptionColorPB.Orange:
-      return 'bg-tint-4';
+      return 'bg-tint-orange';
     case SelectOptionColorPB.Yellow:
-      return 'bg-tint-5';
+      return 'bg-tint-yellow';
     case SelectOptionColorPB.Lime:
-      return 'bg-tint-6';
+      return 'bg-tint-lime';
     case SelectOptionColorPB.Green:
-      return 'bg-tint-7';
+      return 'bg-tint-green';
     case SelectOptionColorPB.Aqua:
-      return 'bg-tint-8';
+      return 'bg-tint-aqua';
     case SelectOptionColorPB.Blue:
-      return 'bg-tint-9';
+      return 'bg-tint-blue';
     default:
       return '';
   }

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorCheckSvg.tsx

@@ -1,10 +1,10 @@
 export const EditorCheckSvg = () => {
   return (
     <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
-      <rect x='2' y='2' width='12' height='12' rx='4' fill={'var(--color-fill-default)'} />
+      <rect x='2' y='2' width='12' height='12' rx='4' fill={'var(--fill-default)'} />
       <path
         d='M6 8L7.61538 9.5L10.5 6.5'
-        stroke={'var(--color-content-onfill)'}
+        stroke={'var(--content-on-fill)'}
         strokeLinecap='round'
         strokeLinejoin='round'
       />

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EditorUncheckSvg.tsx

@@ -1,7 +1,7 @@
 export const EditorUncheckSvg = () => {
   return (
     <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
-      <rect x='2.5' y='2.5' width='11' height='11' rx='3.5' stroke={'var(--color-icon-secondary)'} />
+      <rect x='2.5' y='2.5' width='11' height='11' rx='3.5' stroke={'var(--line-border)'} />
     </svg>
   );
 };

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/FullView.tsx

@@ -1,10 +1,10 @@
 export const FullView = () => {
   return (
     <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
-      <path d='M6 13H3V10' stroke='var(--color-text-title)' strokeLinecap='round' strokeLinejoin='round' />
-      <path d='M10 3H13V6' stroke='var(--color-text-title)' strokeLinecap='round' strokeLinejoin='round' />
-      <path d='M3 13L7 9' stroke='var(--color-text-title)' strokeLinecap='round' strokeLinejoin='round' />
-      <path d='M13 3L9 7' stroke='var(--color-text-title)' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M6 13H3V10' stroke='var(--text-title)' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M10 3H13V6' stroke='var(--text-title)' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M3 13L7 9' stroke='var(--text-title)' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M13 3L9 7' stroke='var(--text-title)' strokeLinecap='round' strokeLinejoin='round' />
     </svg>
   );
 };

+ 5 - 5
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupBySvg.tsx

@@ -3,29 +3,29 @@ export const GroupBySvg = () => {
     <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
       <path
         d='M10 2H13C13.5523 2 14 2.44772 14 3V6'
-        stroke='var(--color-text-title)'
+        stroke='var(--text-title)'
         strokeLinecap='round'
         strokeLinejoin='round'
       />
       <path
         d='M6 2H3C2.44772 2 2 2.44772 2 3V6'
-        stroke='var(--color-text-title)'
+        stroke='var(--text-title)'
         strokeLinecap='round'
         strokeLinejoin='round'
       />
       <path
         d='M6 14H3C2.44772 14 2 13.5523 2 13V10'
-        stroke='var(--color-text-title)'
+        stroke='var(--text-title)'
         strokeLinecap='round'
         strokeLinejoin='round'
       />
       <path
         d='M10 14H13C13.5523 14 14 13.5523 14 13V10'
-        stroke='var(--color-text-title)'
+        stroke='var(--text-title)'
         strokeLinecap='round'
         strokeLinejoin='round'
       />
-      <rect x='6' y='6' width='4' height='4' rx='1' stroke='var(--color-text-title)' />
+      <rect x='6' y='6' width='4' height='4' rx='1' stroke='var(--text-title)' />
     </svg>
   );
 };

+ 6 - 3
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx

@@ -64,9 +64,12 @@ export const BoardCard = ({
             {...provided.draggableProps}
             {...provided.dragHandleProps}
             onClick={() => onOpenRow(rowInfo)}
-            className={`relative cursor-pointer select-none rounded-lg border border-line-border bg-bg-body px-3 py-2 transition-transform duration-100 hover:bg-fill-selector `}
+            className={`relative cursor-pointer select-none rounded-lg bg-bg-body px-3 py-2 transition-transform duration-100 hover:bg-content-blue-50 `}
           >
-            <button onClick={onDetailClick} className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-fill-hover'}>
+            <button
+              onClick={onDetailClick}
+              className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-fill-list-hover'}
+            >
               <Details2Svg></Details2Svg>
             </button>
             <div className={'flex flex-col gap-3'}>
@@ -95,7 +98,7 @@ export const BoardCard = ({
         >
           <button
             key={index}
-            className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+            className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-list-hover'}
             onClick={() => onDeleteRowClick()}
           >
             <i className={'h-5 w-5'}>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardFieldsPopup.tsx

@@ -15,7 +15,7 @@ export const BoardFieldsPopup = ({ hidePopup }: { hidePopup: () => void }) => {
     <div ref={ref} className={'absolute left-full top-full z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md'}>
       {columns.map((column, index) => (
         <div
-          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-hover'}
+          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-list-hover'}
           key={index}
         >
           <div className={'flex items-center gap-2 '}>

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx

@@ -52,10 +52,10 @@ export const BoardGroup = ({
           <span className={'text-shade-4'}>({group.rows.length})</span>
         </div>
         <div className={'flex items-center gap-2'}>
-          <button className={'h-5 w-5 rounded hover:bg-fill-hover'}>
+          <button className={'h-5 w-5 rounded hover:bg-fill-list-hover'}>
             <Details2Svg></Details2Svg>
           </button>
-          <button className={'h-5 w-5 rounded hover:bg-fill-hover'}>
+          <button className={'h-5 w-5 rounded hover:bg-fill-list-hover'}>
             <AddSvg></AddSvg>
           </button>
         </div>
@@ -86,7 +86,7 @@ export const BoardGroup = ({
       <div className={'p-2'}>
         <button
           onClick={onNewRowClick}
-          className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-hover'}
+          className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-fill-list-hover'}
         >
           <span className={'h-5 w-5'}>
             <AddSvg></AddSvg>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroupFieldsPopup.tsx

@@ -15,7 +15,7 @@ export const BoardGroupFieldsPopup = ({ hidePopup }: { hidePopup: () => void })
     <div ref={ref} className={'absolute left-full top-full z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md'}>
       {columns.map((column, index) => (
         <div
-          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-hover'}
+          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-fill-list-hover'}
           key={index}
         >
           <div className={'flex items-center gap-2 '}>

+ 13 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenu.tsx

@@ -8,6 +8,7 @@ import { Keyboard } from '$app/constants/document/keyboard';
 import { selectOptionByUpDown } from '$app/utils/document/menu';
 import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
 import { BlockType } from '$app/interfaces/document';
+import { useTranslation } from 'react-i18next';
 
 enum BlockMenuOption {
   Duplicate = 'Duplicate',
@@ -27,6 +28,7 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
   const { node } = useSubscribeNode(id);
   const [subMenuOpened, setSubMenuOpened] = useState(false);
   const [hovered, setHovered] = useState<BlockMenuOption | null>(null);
+  const { t } = useTranslation();
 
   useEffect(() => {
     if (hovered !== BlockMenuOption.TurnInto) {
@@ -53,7 +55,7 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
           operate: () => {
             return handleClick({ operate: handleDelete });
           },
-          title: 'Delete',
+          title: t('document.plugins.optionAction.delete'),
           icon: <Delete />,
           key: BlockMenuOption.Delete,
         },
@@ -61,7 +63,7 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
           operate: () => {
             return handleClick({ operate: handleDuplicate });
           },
-          title: 'Duplicate',
+          title: t('document.plugins.optionAction.duplicate'),
           icon: <ContentCopy />,
           key: BlockMenuOption.Duplicate,
         },
@@ -69,9 +71,10 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
           ? null
           : {
               key: BlockMenuOption.TurnInto,
+              title: t('document.plugins.optionAction.turnInto'),
             },
       ].filter((item) => item !== null) as Option[],
-    [excludeTurnIntoBlock, handleClick, handleDelete, handleDuplicate]
+    [excludeTurnIntoBlock, handleClick, handleDelete, handleDuplicate, t]
   );
 
   const onKeyDown = useCallback(
@@ -128,13 +131,19 @@ function BlockMenu({ id, onClose }: { id: string; onClose: () => void }) {
       }}
     >
       <div className={'p-2'}>
-        <TextField autoFocus label='Search' placeholder='Search actions...' variant='standard' />
+        <TextField
+          autoFocus
+          label={t('search.label')}
+          placeholder={t('search.placeholder.actions')}
+          variant='standard'
+        />
       </div>
       {options.map((option) => {
         if (option.key === BlockMenuOption.TurnInto) {
           return (
             <BlockMenuTurnInto
               key={option.key}
+              lable={option.title}
               onHovered={() => {
                 setHovered(BlockMenuOption.TurnInto);
                 setSubMenuOpened(true);

+ 3 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockMenuTurnInto.tsx

@@ -9,12 +9,14 @@ function BlockMenuTurnInto({
   onHovered,
   isHovered,
   menuOpened,
+  lable,
 }: {
   id: string;
   onClose: () => void;
   onHovered: (e: MouseEvent) => void;
   isHovered: boolean;
   menuOpened: boolean;
+  lable?: string;
 }) {
   const ref = useRef<HTMLDivElement | null>(null);
   const [anchorPosition, setAnchorPosition] = React.useState<{ top: number; left: number }>();
@@ -37,7 +39,7 @@ function BlockMenuTurnInto({
     <>
       <MenuItem
         ref={ref}
-        title='Turn into'
+        title={lable}
         isHovered={isHovered}
         icon={<Transform />}
         extra={<ArrowRight />}

+ 4 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx

@@ -11,10 +11,12 @@ import { addBlockBelowClickThunk } from '$app_reducers/document/async-actions/me
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 import { RANGE_NAME, RECT_RANGE_NAME } from '$app/constants/document/name';
 import { setRectSelectionThunk } from '$app_reducers/document/async-actions/rect_selection';
+import { useTranslation } from 'react-i18next';
 
 export default function BlockSideToolbar({ container }: { container: HTMLDivElement }) {
   const dispatch = useAppDispatch();
   const { docId, controller } = useSubscribeDocument();
+  const { t } = useTranslation();
 
   const { nodeId, style, ref } = useBlockSideToolbar({ container });
   const isDragging = useAppSelector(
@@ -42,7 +44,7 @@ export default function BlockSideToolbar({ container }: { container: HTMLDivElem
         >
           {/** Add Block below */}
           <ToolbarButton
-            tooltip={'Add a new block below'}
+            tooltip={t('tooltip.addBlockBelow')}
             onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
               if (!nodeId || !controller) return;
               dispatch(
@@ -58,7 +60,7 @@ export default function BlockSideToolbar({ container }: { container: HTMLDivElem
 
           {/** Open menu or drag */}
           <ToolbarButton
-            tooltip={'Click to open Menu'}
+            tooltip={t('tooltip.openMenu')}
             onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
               if (!nodeId) return;
               dispatch(

+ 2 - 3
frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/BlockSlashMenu.tsx

@@ -27,7 +27,6 @@ import { useSubscribeDocument } from '$app/components/document/_shared/Subscribe
 import { slashCommandActions } from '$app_reducers/document/slice';
 import { Keyboard } from '$app/constants/document/keyboard';
 import { selectOptionByUpDown } from '$app/utils/document/menu';
-import { blockEditActions } from '$app_reducers/document/block_edit_slice';
 
 function BlockSlashMenu({
   id,
@@ -60,7 +59,7 @@ function BlockSlashMenu({
       );
       onClose?.();
     },
-    [controller, dispatch, docId, id, onClose]
+    [controller, dispatch, id, onClose]
   );
 
   const options: (SlashCommandOption & {
@@ -293,7 +292,7 @@ function BlockSlashMenu({
       <div ref={ref} className={'min-h-0 flex-1 overflow-y-auto overflow-x-hidden'}>
         {Object.entries(optionsByGroup).map(([group, options]) => (
           <div key={group}>
-            <div className={'px-2 py-2 text-sm text-shade-3'}>{group}</div>
+            <div className={'text-shade-3 px-2 py-2 text-sm'}>{group}</div>
             <div>
               {options.map((option) => {
                 return (

+ 1 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSlash/index.tsx

@@ -1,8 +1,7 @@
-import React, { useEffect } from 'react';
+import React from 'react';
 import Popover from '@mui/material/Popover';
 import BlockSlashMenu from '$app/components/document/BlockSlash/BlockSlashMenu';
 import { useBlockSlash } from '$app/components/document/BlockSlash/index.hooks';
-import { Keyboard } from '$app/constants/document/keyboard';
 
 function BlockSlash({ container }: { container: HTMLDivElement }) {
   const { blockId, open, onClose, anchorPosition, searchText, hoverOption } = useBlockSlash();

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/CalloutBlock/index.tsx

@@ -17,7 +17,7 @@ export default function CalloutBlock({
   const { openEmojiSelect, open, closeEmojiSelect, id, anchorEl, onEmojiSelect } = useCalloutBlock(node.id);
 
   return (
-    <div className={'my-1 flex rounded border border-solid border-line-border bg-fill-selector p-4'}>
+    <div className={'my-1 flex rounded border border-solid border-line-divider bg-content-blue-50 p-4'}>
       <div className={'w-[1.5em]'} onMouseDown={(e) => e.stopPropagation()}>
         <div className={'flex h-[calc(1.5em_+_2px)] w-[24px] select-none items-center justify-start'}>
           <IconButton

+ 6 - 3
frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/SelectLanguage.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useContext } from 'react';
+import React, { useCallback } from 'react';
 import MenuItem from '@mui/material/MenuItem';
 import FormControl from '@mui/material/FormControl';
 import Select, { SelectChangeEvent } from '@mui/material/Select';
@@ -6,15 +6,17 @@ import { updateNodeDataThunk } from '$app_reducers/document/async-actions';
 import { useAppDispatch } from '$app/stores/store';
 import { supportLanguage } from '$app/constants/document/code';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
+import { useTranslation } from 'react-i18next';
 
 function SelectLanguage({ id, language }: { id: string; language: string }) {
   const dispatch = useAppDispatch();
   const { controller } = useSubscribeDocument();
-
+  const { t } = useTranslation();
   const onLanguageSelect = useCallback(
     (event: SelectChangeEvent) => {
       if (!controller) return;
       const language = event.target.value;
+
       dispatch(
         updateNodeDataThunk({
           id,
@@ -34,7 +36,8 @@ function SelectLanguage({ id, language }: { id: string; language: string }) {
         className={'h-[28px] w-[150px]'}
         value={language || 'javascript'}
         onChange={onLanguageSelect}
-        label='Language'
+        placeholder={t('document.codeBlock.language.placeholder')}
+        label={t('document.codeBlock.language.label')}
       >
         {supportLanguage.map((item) => (
           <MenuItem key={item.id} value={item.id}>

+ 4 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/CodeBlock/index.tsx

@@ -22,7 +22,10 @@ export default function CodeBlock({
   const isDark = useAppSelector((state) => state.currentUser.userSetting.themeMode === ThemeMode.Dark);
 
   return (
-    <div {...props} className={`my-1 rounded border border-solid border-line-border bg-fill-selector p-6 ${className}`}>
+    <div
+      {...props}
+      className={`my-1 rounded border border-solid border-line-divider bg-content-blue-50 p-6 ${className}`}
+    >
       <div className={'mb-2 w-[100%]'}>
         <SelectLanguage id={id} language={language} />
       </div>

+ 4 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/DocumentTitle/index.tsx

@@ -1,13 +1,16 @@
 import React from 'react';
 import { useDocumentTitle } from './DocumentTitle.hooks';
 import TextBlock from '../TextBlock';
+import { useTranslation } from 'react-i18next';
 
 export default function DocumentTitle({ id }: { id: string }) {
   const { node } = useDocumentTitle(id);
+  const { t } = useTranslation();
+
   if (!node) return null;
   return (
     <div data-block-id={node.id} className='doc-title relative mb-2 pt-[50px] text-4xl font-bold'>
-      <TextBlock placeholder='Untitled' childIds={[]} node={node} />
+      <TextBlock placeholder={t('document.title.placeholder')} childIds={[]} node={node} />
     </div>
   );
 }

+ 6 - 3
frontend/appflowy_tauri/src/appflowy_app/components/document/EquationBlock/index.tsx

@@ -7,6 +7,7 @@ import { useBlockPopover } from '$app/components/document/_shared/BlockPopover/B
 import { updateNodeDataThunk } from '$app_reducers/document/async-actions';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 import { useAppDispatch } from '$app/stores/store';
+import { useTranslation } from 'react-i18next';
 
 function EquationBlock({ node }: { node: NestedBlock<BlockType.EquationBlock> }) {
   const formula = node.data.formula;
@@ -60,19 +61,21 @@ function EquationBlock({ node }: { node: NestedBlock<BlockType.EquationBlock> })
   });
   const displayFormula = open ? value : formula;
 
+  const { t } = useTranslation();
+
   return (
     <>
       <div
         ref={anchorElRef}
         onClick={openPopover}
-        className={'my-1 flex min-h-[59px] cursor-pointer flex-col overflow-hidden rounded hover:bg-fill-selector'}
+        className={'my-1 flex min-h-[59px] cursor-pointer flex-col overflow-hidden rounded hover:bg-content-blue-50'}
       >
         {displayFormula ? (
           <KatexMath latex={displayFormula} />
         ) : (
-          <div className={'flex h-[100%] w-[100%] flex-1 items-center bg-fill-selector px-1 text-text-title'}>
+          <div className={'flex h-[100%] w-[100%] flex-1 items-center bg-content-blue-50 px-1 text-text-caption'}>
             <Functions />
-            <span>Add a TeX equation</span>
+            <span>{t('document.plugins.mathEquation.addMathEquation')}</span>
           </div>
         )}
       </div>

+ 10 - 11
frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/EditImage.tsx

@@ -4,7 +4,7 @@ import { useAppDispatch } from '$app/stores/store';
 import { updateNodeDataThunk } from '$app_reducers/document/async-actions';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 import UploadImage from '$app/components/document/_shared/UploadImage';
-import { isTauri } from '$app/utils/env';
+import { useTranslation } from 'react-i18next';
 
 enum TAB_KEYS {
   UPLOAD = 'upload',
@@ -13,6 +13,7 @@ enum TAB_KEYS {
 
 function EditImage({ id, url, onClose }: { id: string; url: string; onClose: () => void }) {
   const dispatch = useAppDispatch();
+  const { t } = useTranslation();
   const { controller } = useSubscribeDocument();
   const [linkVal, setLinkVal] = useState<string>(url);
   const [tabKey, setTabKey] = useState<TAB_KEYS>(TAB_KEYS.UPLOAD);
@@ -41,31 +42,29 @@ function EditImage({ id, url, onClose }: { id: string; url: string; onClose: ()
     <div className={'w-[540px]'}>
       <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
         <Tabs value={tabKey} onChange={handleChange}>
-          {isTauri() && <Tab label={'Upload Image'} value={TAB_KEYS.UPLOAD} />}
+          <Tab label={t('document.imageBlock.upload.label')} value={TAB_KEYS.UPLOAD} />
 
-          <Tab label='URL Image' value={TAB_KEYS.LINK} />
+          <Tab label={t('document.imageBlock.url.label')} value={TAB_KEYS.LINK} />
         </Tabs>
       </Box>
-      {isTauri() && (
-        <TabPanel value={tabKey} index={TAB_KEYS.UPLOAD}>
-          <UploadImage onChange={handleConfirmUrl} />
-        </TabPanel>
-      )}
+      <TabPanel value={tabKey} index={TAB_KEYS.UPLOAD}>
+        <UploadImage onChange={handleConfirmUrl} />
+      </TabPanel>
 
       <TabPanel className={'flex flex-col p-3'} value={tabKey} index={TAB_KEYS.LINK}>
         <TextField
           value={linkVal}
           onChange={(e) => setLinkVal(e.target.value)}
           variant='outlined'
-          label={'URL'}
+          label={t('document.imageBlock.url.label')}
           autoFocus={true}
           style={{
             marginBottom: '10px',
           }}
-          placeholder={'Please enter the URL of the image'}
+          placeholder={t('document.imageBlock.url.placeholder')}
         />
         <Button onClick={() => handleConfirmUrl(linkVal)} variant='contained'>
-          Upload
+          {t('button.upload')}
         </Button>
       </TabPanel>
     </div>

+ 6 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx

@@ -4,8 +4,9 @@ import { useSubscribeDocument } from '$app/components/document/_shared/Subscribe
 import { Align } from '$app/interfaces/document';
 import { FormatAlignCenter, FormatAlignLeft, FormatAlignRight } from '@mui/icons-material';
 import { updateNodeDataThunk } from '$app_reducers/document/async-actions';
-import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip';
+import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip';
 import Popover from '@mui/material/Popover';
+import { useTranslation } from 'react-i18next';
 
 function ImageAlign({
   id,
@@ -21,6 +22,7 @@ function ImageAlign({
   const ref = useRef<HTMLDivElement | null>(null);
   const [anchorEl, setAnchorEl] = useState<HTMLDivElement>();
   const popoverOpen = Boolean(anchorEl);
+  const { t } = useTranslation();
 
   useEffect(() => {
     if (popoverOpen) {
@@ -61,7 +63,7 @@ function ImageAlign({
 
   return (
     <>
-      <MenuTooltip title='Align'>
+      <ToolbarTooltip title={t('document.plugins.optionAction.align')}>
         <div
           ref={ref}
           className='flex items-center justify-center p-1'
@@ -71,7 +73,7 @@ function ImageAlign({
         >
           {renderAlign(align)}
         </div>
-      </MenuTooltip>
+      </ToolbarTooltip>
       <Popover
         open={popoverOpen}
         anchorOrigin={{
@@ -87,7 +89,7 @@ function ImageAlign({
         onClose={() => setAnchorEl(undefined)}
         PaperProps={{
           style: {
-            backgroundColor: 'var(--color-bg-body)',
+            backgroundColor: 'var(--bg-body)',
           },
         }}
       >

+ 4 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 import { Alert, CircularProgress } from '@mui/material';
 import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
+import { useTranslation } from 'react-i18next';
 
 function ImagePlaceholder({
   error,
@@ -20,6 +21,7 @@ function ImagePlaceholder({
   openPopover: () => void;
 }) {
   const visible = loading || error || isEmpty;
+  const { t } = useTranslation();
 
   return (
     <div
@@ -40,12 +42,12 @@ function ImagePlaceholder({
       {isEmpty && (
         <div
           onClick={openPopover}
-          className={'flex h-[100%] w-[100%] flex-1 items-center rounded bg-fill-selector px-1 text-text-title'}
+          className={'flex h-[100%] w-[100%] flex-1 items-center rounded bg-content-blue-50 px-1 text-text-caption'}
         >
           <i className={'mx-2 h-5 w-5'}>
             <ImageSvg />
           </i>
-          <span>Add an image</span>
+          <span>{t('document.imageBlock.placeholder')}</span>
         </div>
       )}
     </div>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx

@@ -29,7 +29,7 @@ function ImageRender({
           } top-0 flex h-[100%] w-[15px] cursor-col-resize items-center justify-center`}
         >
           <div
-            className={`h-[48px] max-h-[50%] w-2 rounded-[20px] border border-solid border-line-border bg-line-border ${
+            className={`h-[48px] max-h-[50%] w-2 rounded-[20px] border border-solid border-line-divider bg-line-border ${
               toolbarOpen ? 'opacity-1' : 'opacity-0'
             } transition-opacity duration-300 `}
           />

+ 6 - 3
frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx

@@ -1,11 +1,12 @@
 import React, { useState } from 'react';
 import { Align } from '$app/interfaces/document';
 import ImageAlign from '$app/components/document/ImageBlock/ImageAlign';
-import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip';
+import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip';
 import { DeleteOutline } from '@mui/icons-material';
 import { useAppDispatch } from '$app/stores/store';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 import { deleteNodeThunk } from '$app_reducers/document/async-actions';
+import { useTranslation } from 'react-i18next';
 
 function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: Align }) {
   const [popoverOpen, setPopoverOpen] = useState(false);
@@ -13,6 +14,8 @@ function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: A
   const dispatch = useAppDispatch();
   const { controller } = useSubscribeDocument();
 
+  const { t } = useTranslation();
+
   return (
     <>
       <div
@@ -21,7 +24,7 @@ function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: A
         } absolute right-2 top-2 z-[1px] flex h-[26px] max-w-[calc(100%-16px)] cursor-pointer items-center justify-center whitespace-nowrap rounded bg-bg-body text-sm text-text-title transition-opacity`}
       >
         <ImageAlign id={id} align={align} onOpen={() => setPopoverOpen(true)} onClose={() => setPopoverOpen(false)} />
-        <MenuTooltip title={'Delete'}>
+        <ToolbarTooltip title={t('button.delete')}>
           <div
             onClick={() => {
               dispatch(deleteNodeThunk({ id, controller }));
@@ -30,7 +33,7 @@ function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: A
           >
             <DeleteOutline />
           </div>
-        </MenuTooltip>
+        </ToolbarTooltip>
       </div>
     </>
   );

+ 5 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx

@@ -19,6 +19,7 @@ import CodeBlock from '$app/components/document/CodeBlock';
 import { NodeIdContext } from '$app/components/document/_shared/SubscribeNode.hooks';
 import EquationBlock from '$app/components/document/EquationBlock';
 import ImageBlock from '$app/components/document/ImageBlock';
+import { useTranslation } from 'react-i18next';
 
 function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
   const { node, childIds, isSelected, ref } = useNode(id);
@@ -82,7 +83,7 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<H
         {renderBlock()}
         <BlockOverlay id={id} />
         {isSelected ? (
-          <div className='pointer-events-none absolute inset-0 z-[-1] my-[1px] rounded-[4px] bg-fill-hover' />
+          <div className='pointer-events-none absolute inset-0 z-[-1] my-[1px] rounded-[4px] bg-content-blue-100' />
         ) : null}
       </div>
     </NodeIdContext.Provider>
@@ -94,9 +95,11 @@ const NodeWithErrorBoundary = withErrorBoundary(NodeComponent, {
 });
 
 const UnSupportedBlock = () => {
+  const { t } = useTranslation();
+
   return (
     <Alert severity='info' className='mb-2'>
-      <p>The current version does not support this Block.</p>
+      <p>{t('unSupportBlock')}</p>
     </Alert>
   );
 };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx

@@ -18,7 +18,7 @@ const TextActionComponent = ({ container }: { container: HTMLDivElement }) => {
         style={{
           opacity: 0,
         }}
-        className='absolute mt-[-6px] inline-flex h-[32px] min-w-[100px] items-stretch overflow-hidden rounded-[8px] bg-bg-base leading-tight text-text-title shadow-lg transition-opacity duration-100'
+        className='absolute mt-[-6px] inline-flex h-[32px] min-w-[100px] items-stretch overflow-hidden rounded-[8px] bg-fill-toolbar leading-tight text-content-on-fill shadow-md transition-opacity duration-100'
         onMouseDown={(e) => {
           // prevent toolbar from taking focus away from editor
           e.preventDefault();

+ 16 - 16
frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx

@@ -1,7 +1,6 @@
-import IconButton from '@mui/material/IconButton';
 import React, { useCallback, useEffect, useMemo } from 'react';
 import { TemporaryType, TextAction } from '$app/interfaces/document';
-import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip';
+import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip';
 import { getFormatActiveThunk, toggleFormatThunk } from '$app_reducers/document/async-actions/format';
 import { useAppDispatch, useAppSelector } from '$app/stores/store';
 import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
@@ -18,18 +17,19 @@ import {
   StrikethroughSOutlined,
 } from '@mui/icons-material';
 import LinkIcon from '@mui/icons-material/AddLink';
+import { useTranslation } from 'react-i18next';
 
 export const iconSize = { width: 18, height: 18 };
 
 const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => {
   const dispatch = useAppDispatch();
   const { docId, controller } = useSubscribeDocument();
-
+  const { t } = useTranslation();
   const focusId = useAppSelector((state) => state[RANGE_NAME][docId]?.focus?.id || '');
   const { node: focusNode } = useSubscribeNode(focusId);
 
   const [isActive, setIsActive] = React.useState(false);
-  const color = useMemo(() => (isActive ? 'text-content-hover' : 'text-text-title'), [isActive]);
+  const color = useMemo(() => (isActive ? 'text-content-on-fill-hover' : ''), [isActive]);
 
   const isFormatActive = useCallback(async () => {
     if (!focusNode) return false;
@@ -82,15 +82,15 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) =>
 
   const formatTooltips: Record<string, string> = useMemo(
     () => ({
-      [TextAction.Bold]: 'Bold',
-      [TextAction.Italic]: 'Italic',
-      [TextAction.Underline]: 'Underline',
-      [TextAction.Strikethrough]: 'Strike through',
-      [TextAction.Code]: 'Mark as Code',
-      [TextAction.Link]: 'Add Link',
-      [TextAction.Equation]: 'Create equation',
+      [TextAction.Bold]: t('toolbar.bold'),
+      [TextAction.Italic]: t('toolbar.italic'),
+      [TextAction.Underline]: t('toolbar.underline'),
+      [TextAction.Strikethrough]: t('toolbar.strike'),
+      [TextAction.Code]: t('toolbar.inlineCode'),
+      [TextAction.Link]: t('toolbar.addLink'),
+      [TextAction.Equation]: t('document.plugins.mathEquation.addMathEquation'),
     }),
-    []
+    [t]
   );
 
   const formatClick = useCallback(
@@ -132,7 +132,7 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) =>
                 marginRight: '0.25rem',
               }}
             />
-            <div className={'underline'}>Link</div>
+            <div className={'underline'}>{t('toolbar.link')}</div>
           </div>
         );
       case TextAction.Equation:
@@ -140,14 +140,14 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) =>
       default:
         return null;
     }
-  }, [icon]);
+  }, [icon, t]);
 
   return (
-    <MenuTooltip title={formatTooltips[format]}>
+    <ToolbarTooltip title={formatTooltips[format]}>
       <div className={`${color} cursor-pointer px-1 hover:text-fill-default`} onClick={() => formatClick(format)}>
         {formatIcon}
       </div>
-    </MenuTooltip>
+    </ToolbarTooltip>
   );
 };
 

+ 0 - 16
frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx

@@ -1,16 +0,0 @@
-import React from 'react';
-import Tooltip from '@mui/material/Tooltip';
-
-function MenuTooltip({ title, children }: { children: JSX.Element; title?: string }) {
-  return (
-    <Tooltip
-      slotProps={{ tooltip: { style: { background: '#E0F8FF', borderRadius: 8 } } }}
-      title={title}
-      placement='top-start'
-    >
-      <div>{children}</div>
-    </Tooltip>
-  );
-}
-
-export default MenuTooltip;

+ 5 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx

@@ -1,9 +1,9 @@
 import React, { useCallback } from 'react';
 import TurnIntoPopover from '$app/components/document/_shared/TurnInto';
-import Button from '@mui/material/Button';
 import ArrowDropDown from '@mui/icons-material/ArrowDropDown';
-import MenuTooltip from './MenuTooltip';
 import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks';
+import { useTranslation } from 'react-i18next';
+import ToolbarTooltip from '../../_shared/ToolbarTooltip';
 
 function TurnIntoSelect({ id }: { id: string }) {
   const [anchorPosition, setAnchorPosition] = React.useState<{
@@ -26,15 +26,16 @@ function TurnIntoSelect({ id }: { id: string }) {
   }, []);
 
   const open = Boolean(anchorPosition);
+  const { t } = useTranslation();
 
   return (
     <>
-      <MenuTooltip title='Turn into'>
+      <ToolbarTooltip title={t('document.plugins.optionAction.turnInto')}>
         <div onClick={handleClick} className='flex cursor-pointer items-center px-2 text-sm text-fill-default'>
           <span>{node.type}</span>
           <ArrowDropDown />
         </div>
-      </MenuTooltip>
+      </ToolbarTooltip>
       <TurnIntoPopover
         id={id}
         open={open}

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/index.tsx

@@ -31,7 +31,7 @@ function TextActionMenuList() {
       {groupItems.map(
         (group, i: number) =>
           group.length > 0 && (
-            <div className={'flex border-r border-solid border-line-border px-1 last:border-r-0'} key={i}>
+            <div className={'flex border-r border-solid border-line-on-toolbar px-1 last:border-r-0'} key={i}>
               {group.map((item) => (
                 <div key={item} className={'flex items-center'}>
                   {renderNode(item)}

+ 9 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx

@@ -5,6 +5,7 @@ import { useChange } from '$app/components/document/_shared/EditorHooks/useChang
 import NodeChildren from '$app/components/document/Node/NodeChildren';
 import { useKeyDown } from '$app/components/document/TextBlock/useKeyDown';
 import { useSelection } from '$app/components/document/_shared/EditorHooks/useSelection';
+import { useTranslation } from 'react-i18next';
 
 interface Props {
   node: NestedBlock;
@@ -15,10 +16,17 @@ function TextBlock({ node, childIds, placeholder }: Props) {
   const { value, onChange } = useChange(node);
   const selectionProps = useSelection(node.id);
   const { onKeyDown } = useKeyDown(node.id);
+  const { t } = useTranslation();
 
   return (
     <>
-      <Editor value={value} onChange={onChange} {...selectionProps} onKeyDown={onKeyDown} placeholder={placeholder} />
+      <Editor
+        value={value}
+        onChange={onChange}
+        {...selectionProps}
+        onKeyDown={onKeyDown}
+        placeholder={placeholder || t('document.textBlock.placeholder')}
+      />
       <NodeChildren className='pl-[1.5em]' childIds={childIds} />
     </>
   );

+ 18 - 11
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx

@@ -17,15 +17,18 @@ export function useBlockPopover({
   onAfterOpen?: () => void;
   renderContent: ({ onClose }: { onClose: () => void }) => React.ReactNode;
 }) {
-  const anchorElRef = useRef<HTMLDivElement>(null);
+  const anchorElRef = useRef<HTMLDivElement | null>(null);
   const { docId } = useSubscribeDocument();
 
-  const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>(null);
-  const open = Boolean(anchorEl);
+  const [anchorPosition, setAnchorPosition] = useState<{
+    top: number;
+    left: number;
+  }>();
+  const open = Boolean(anchorPosition);
   const editing = useEditingState(id);
   const dispatch = useAppDispatch();
   const closePopover = useCallback(() => {
-    setAnchorEl(null);
+    setAnchorPosition(undefined);
     dispatch(
       blockEditActions.setBlockEditState({
         id: docId,
@@ -48,7 +51,14 @@ export function useBlockPopover({
   }, [dispatch, docId, id]);
 
   const openPopover = useCallback(() => {
-    setAnchorEl(anchorElRef.current);
+    if (!anchorElRef.current) return;
+
+    const rect = anchorElRef.current.getBoundingClientRect();
+
+    setAnchorPosition({
+      top: rect.top + rect.height,
+      left: rect.left + rect.width / 2,
+    });
     selectBlock();
     onAfterOpen?.();
   }, [onAfterOpen, selectBlock]);
@@ -68,21 +78,18 @@ export function useBlockPopover({
           vertical: 'top',
           horizontal: 'center',
         }}
-        anchorOrigin={{
-          vertical: 'bottom',
-          horizontal: 'center',
-        }}
         onMouseDown={(e) => e.stopPropagation()}
         onClose={closePopover}
         open={open}
-        anchorEl={anchorEl}
+        anchorReference={'anchorPosition'}
+        anchorPosition={anchorPosition}
       >
         {renderContent({
           onClose: closePopover,
         })}
       </Popover>
     );
-  }, [anchorEl, closePopover, open, renderContent]);
+  }, [anchorPosition, closePopover, open, renderContent]);
 
   useEffect(() => {
     if (!anchorElRef.current) {

+ 1 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx

@@ -132,6 +132,7 @@ function InlineContainer({
         style={{
           pointerEvents: 'none',
         }}
+        className={'inline-block-content'}
       >
         {renderNode()}
       </span>

+ 14 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx

@@ -1,6 +1,5 @@
 import React, { useCallback, useMemo, useState } from 'react';
-import { Portal, Snackbar } from '@mui/material';
-import { TransitionProps } from '@mui/material/transitions';
+import { Alert, Portal, Snackbar } from '@mui/material';
 import Slide, { SlideProps } from '@mui/material/Slide';
 
 function SlideTransition(props: SlideProps) {
@@ -11,6 +10,7 @@ interface MessageProps {
   message?: string;
   key?: string;
   duration?: number;
+  type?: 'success' | 'error';
 }
 export function useMessage() {
   const [state, setState] = useState<MessageProps>();
@@ -23,6 +23,7 @@ export function useMessage() {
 
   const contentHolder = useMemo(() => {
     const open = !!state;
+
     return (
       <Portal>
         <Snackbar
@@ -31,10 +32,19 @@ export function useMessage() {
           open={open}
           onClose={hide}
           TransitionProps={{ onExited: hide }}
-          message={state?.message}
           key={state?.key}
           TransitionComponent={SlideTransition}
-        />
+        >
+          <>
+            {state?.type ? (
+              <Alert severity={state.type} sx={{ width: '100%' }}>
+                {state.message}
+              </Alert>
+            ) : (
+              <span>{state?.message}</span>
+            )}
+          </>
+        </Snackbar>
       </Portal>
     );
   }, [hide, state]);

+ 10 - 6
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx

@@ -37,10 +37,10 @@ const TextLeaf = (props: TextLeafProps) => {
   };
   let newChildren = children;
 
-  if (leaf.code) {
+  if (leaf.code && !leaf.temporary) {
     newChildren = (
       <span
-        className={`bg-fill-selector text-text-title`}
+        className={`bg-content-blue-50 text-text-title`}
         style={{
           fontSize: '85%',
           lineHeight: 'normal',
@@ -97,9 +97,9 @@ const TextLeaf = (props: TextLeafProps) => {
     isCodeBlock && 'token',
     leaf.prism_token && leaf.prism_token,
     leaf.strikethrough && 'line-through',
-    leaf.selection_high_lighted && 'bg-fill-selector',
-    leaf.link_selection_lighted && 'text-text-link-selector bg-fill-selector',
-    leaf.code && 'inline-code',
+    leaf.selection_high_lighted && 'bg-content-blue-100',
+    leaf.link_selection_lighted && 'text-text-link-selector bg-content-blue-100',
+    leaf.code && !leaf.temporary && 'inline-code',
     leaf.bold && 'font-bold',
     leaf.italic && 'italic',
     leaf.underline && 'underline',
@@ -114,7 +114,11 @@ const TextLeaf = (props: TextLeafProps) => {
   }
 
   if (leaf.temporary) {
-    newChildren = <TemporaryInput leaf={leaf}>{newChildren}</TemporaryInput>;
+    newChildren = (
+      <TemporaryInput getSelection={getSelection} leaf={leaf}>
+        {newChildren}
+      </TemporaryInput>
+    );
   }
 
   return (

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import TextField from '@mui/material/TextField';
 import { CheckOutlined, FunctionsOutlined } from '@mui/icons-material';
-import { Divider, IconButton, InputAdornment } from '@mui/material';
+import { IconButton, InputAdornment } from '@mui/material';
 
 function EquationEditContent({
   value,

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx

@@ -4,7 +4,7 @@ import KatexMath from '$app/components/document/_shared/KatexMath';
 
 function TemporaryEquation({ latex }: { latex: string }) {
   return (
-    <span className={'rounded bg-fill-selector px-1 py-0.5'} contentEditable={false}>
+    <span className={'rounded bg-content-blue-50 px-1 py-0.5'} contentEditable={false}>
       {latex ? (
         <KatexMath latex={latex} isInline />
       ) : (

+ 17 - 14
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx

@@ -1,33 +1,36 @@
 import React, { useCallback, useEffect, useMemo, useRef } from 'react';
-import { TemporaryType } from '$app/interfaces/document';
+import { RangeStaticNoId, TemporaryType } from '$app/interfaces/document';
 import TemporaryEquation from '$app/components/document/_shared/TemporaryInput/TemporaryEquation';
 import { useSubscribeTemporary } from '$app/components/document/_shared/SubscribeTemporary.hooks';
-import { isOverlappingPrefix } from '$app/utils/document/temporary';
 import { PopoverPosition } from '@mui/material';
 import { useAppDispatch } from '$app/stores/store';
 import { temporaryActions } from '$app_reducers/document/temporary_slice';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 
-function TemporaryInput({ leaf, children }: { leaf: { text: string }; children: React.ReactNode }) {
+function TemporaryInput({
+  leaf,
+  children,
+  getSelection,
+}: {
+  leaf: { text: string };
+  children: React.ReactNode;
+  getSelection: (node: Element) => RangeStaticNoId | null;
+}) {
   const temporaryState = useSubscribeTemporary();
   const id = temporaryState?.id;
   const dispatch = useAppDispatch();
   const ref = useRef<HTMLSpanElement>(null);
   const { docId } = useSubscribeDocument();
   const match = useMemo(() => {
+    if (!ref.current) return false;
     if (!leaf.text) return false;
     if (!temporaryState) return false;
-    const { selectedText, type } = temporaryState;
+    const { selectedText } = temporaryState;
+    const selection = getSelection(ref.current);
 
-    switch (type) {
-      case TemporaryType.Equation:
-        // when the leaf is split, the placeholder is not the same as the leaf text,
-        // so we can only check for overlapping prefix and hidden other leafs
-        return leaf.text === selectedText || isOverlappingPrefix(leaf.text, selectedText);
-      default:
-        return false;
-    }
-  }, [temporaryState, leaf.text]);
+    if (!selection) return false;
+    return leaf.text === selectedText || selection.index <= temporaryState.selection.index;
+  }, [leaf.text, temporaryState, getSelection]);
 
   const renderPlaceholder = useCallback(() => {
     if (!temporaryState) return null;
@@ -69,7 +72,7 @@ function TemporaryInput({ leaf, children }: { leaf: { text: string }; children:
   return (
     <span ref={ref}>
       {match ? renderPlaceholder() : null}
-      <span className={'absolute opacity-0'}>{children}</span>
+      <span className={`absolute opacity-0 ${match ? 'w-0' : ''}`}>{children}</span>
     </span>
   );
 }

+ 5 - 3
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx

@@ -5,6 +5,7 @@ import LanguageIcon from '@mui/icons-material/Language';
 import CopyIcon from '@mui/icons-material/CopyAll';
 import { copyText } from '$app/utils/document/copy_paste';
 import { useMessage } from '$app/components/document/_shared/Message';
+import { useTranslation } from 'react-i18next';
 
 const iconSize = {
   width: '1rem',
@@ -28,6 +29,7 @@ function EditLinkToolbar({
   editing: boolean;
   onEdit: () => void;
 }) {
+  const { t } = useTranslation();
   const { show, contentHolder } = useMessage();
   const ref = useRef<HTMLDivElement>(null);
 
@@ -70,9 +72,9 @@ function EditLinkToolbar({
                 onClick={async () => {
                   try {
                     await copyText(href);
-                    show({ message: 'Copied!', duration: 6000 });
+                    show({ message: t('message.copy.success'), duration: 6000 });
                   } catch {
-                    show({ message: 'Copy failed!', duration: 6000 });
+                    show({ message: t('message.copy.fail'), duration: 6000 });
                   }
                 }}
                 className={'mr-2 cursor-pointer'}
@@ -80,7 +82,7 @@ function EditLinkToolbar({
                 <CopyIcon sx={iconSize} />
               </div>
               <div onClick={onEdit} className={'cursor-pointer'}>
-                Edit
+                {t('button.edit')}
               </div>
             </div>
           </div>

+ 5 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx

@@ -8,11 +8,12 @@ import { formatLinkThunk } from '$app_reducers/document/async-actions/link';
 import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
 import { useSubscribeLinkPopover } from '$app/components/document/_shared/SubscribeLinkPopover.hooks';
 import Button from '@mui/material/Button';
+import { useTranslation } from 'react-i18next';
 
 function LinkEditPopover() {
   const dispatch = useAppDispatch();
   const { docId, controller } = useSubscribeDocument();
-
+  const { t } = useTranslation();
   const popoverState = useSubscribeLinkPopover();
   const { anchorPosition, id, selection, title = '', href = '', open = false } = popoverState;
 
@@ -101,7 +102,7 @@ function LinkEditPopover() {
     >
       <div className='flex flex-col p-3'>
         <EditLink
-          text={'URL'}
+          text={t('document.inlineLink.url.label')}
           value={href}
           onChange={(link) => {
             onChange({
@@ -111,7 +112,7 @@ function LinkEditPopover() {
           }}
         />
         <EditLink
-          text={'Link title'}
+          text={t('document.inlineLink.title.label')}
           value={title}
           onChange={(text) =>
             onChange({
@@ -123,7 +124,7 @@ function LinkEditPopover() {
         <div className={'flex items-center justify-end'}>
           <Button onClick={onDone}>
             <Done />
-            Done
+            {t('button.done')}
           </Button>
         </div>
       </div>

+ 16 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import Tooltip from '@mui/material/Tooltip';
+
+function ToolbarTooltip({ title, children }: { children: JSX.Element; title?: string }) {
+  return (
+    <Tooltip
+      slotProps={{ tooltip: { style: { background: 'var(--bg-tips)', borderRadius: 8 } } }}
+      title={title}
+      placement='top-start'
+    >
+      <div>{children}</div>
+    </Tooltip>
+  );
+}
+
+export default ToolbarTooltip;

+ 38 - 14
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx

@@ -1,30 +1,53 @@
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
 import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
 import { CircularProgress } from '@mui/material';
 import { writeImage } from '$app/utils/document/image';
-import { isTauri } from '$app/utils/env';
+import { useTranslation } from 'react-i18next';
+import { useMessage } from '$app/components/document/_shared/Message';
 
 export interface UploadImageProps {
   onChange: (filePath: string) => void;
 }
 
 function UploadImage({ onChange }: UploadImageProps) {
+  const { t } = useTranslation();
+  const message = useMessage();
+
   const inputRef = useRef<HTMLInputElement>(null);
   const [loading, setLoading] = useState<boolean>(false);
   const [error, setError] = useState<string>('');
-  const beforeUpload = useCallback((file: File) => {
-    // check file size and type
-    const sizeMatched = file.size / 1024 / 1024 < 5; // 5MB
-    const typeMatched = /image\/(png|jpg|jpeg|gif)/.test(file.type); // png, jpg, jpeg, gif
+  const beforeUpload = useCallback(
+    (file: File) => {
+      // check file size and type
+      const sizeMatched = file.size / 1024 / 1024 < 5; // 5MB
+      const typeMatched = /image\/(png|jpg|jpeg|gif)/.test(file.type); // png, jpg, jpeg, gif
+
+      if (!sizeMatched) {
+        setError(t('document.imageBlock.error.invalidImageSize'));
+      }
 
-    return sizeMatched && typeMatched;
-  }, []);
+      if (!typeMatched) {
+        setError(t('document.imageBlock.error.invalidImageFormat'));
+      }
+
+      return sizeMatched && typeMatched;
+    },
+    [t]
+  );
+
+  useEffect(() => {
+    if (!error) return;
+    message.show({
+      message: error,
+      duration: 3000,
+      type: 'error',
+    });
+  }, [error]);
 
   const handleUpload = useCallback(
     async (file: File) => {
       if (!file) return;
       if (!beforeUpload(file)) {
-        setError('Image should be less than 5MB and in png, jpg, jpeg, gif format');
         return;
       }
 
@@ -38,10 +61,10 @@ function UploadImage({ onChange }: UploadImageProps) {
         onChange(filePath);
       } catch {
         setLoading(false);
-        setError('Upload failed');
+        setError(t('document.imageBlock.error.invalidImage'));
       }
     },
-    [beforeUpload, onChange]
+    [beforeUpload, onChange, t]
   );
 
   const handleChange = useCallback(
@@ -88,7 +111,7 @@ function UploadImage({ onChange }: UploadImageProps) {
         <input onChange={handleChange} ref={inputRef} type='file' className={'hidden'} accept={'image/*'} />
         <div
           className={
-            'flex flex-col items-center justify-center rounded-md border border-dashed border-content-hover py-10 text-content-hover'
+            'flex flex-col items-center justify-center rounded-md border border-dashed border-content-blue-300 bg-content-blue-50 py-10 text-content-blue-300'
           }
           style={{
             borderColor: errorColor,
@@ -101,7 +124,7 @@ function UploadImage({ onChange }: UploadImageProps) {
           <div className={'h-8 w-8'}>
             <ImageSvg />
           </div>
-          <div className={'my-2 p-2'}>{isTauri() ? 'Click space to chose image' : 'Chose image or drag to space'}</div>
+          <div className={'my-2 p-2'}>{t('document.imageBlock.upload.placeholder')}</div>
         </div>
 
         {loading ? <CircularProgress /> : null}
@@ -112,8 +135,9 @@ function UploadImage({ onChange }: UploadImageProps) {
         }}
         className={`mt-5 text-sm text-text-caption`}
       >
-        The maximum file size is 5MB. Supported formats: JPG, PNG, GIF, SVG.
+        {t('document.imageBlock.support')}
       </div>
+      {message.contentHolder}
     </div>
   );
 }

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx

@@ -2,7 +2,7 @@ import AddSvg from '../../_shared/svg/AddSvg';
 
 export const GridAddView = () => {
   return (
-    <button className='flex cursor-pointer items-center rounded-lg p-2 text-sm hover:bg-fill-hover'>
+    <button className='flex cursor-pointer items-center rounded-lg p-2 text-sm hover:bg-fill-list-hover'>
       <i className='mr-2 h-5 w-5'>
         <AddSvg />
       </i>

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.tsx

@@ -18,15 +18,15 @@ export const GridTableHeader = ({ controller }: { controller: DatabaseController
             return <GridTableHeaderItem field={field} controller={controller} key={i} />;
           })}
 
-          <th className='m-0 w-40 border border-r-0 border-line-border p-0'>
+          <th className='m-0 w-40 border border-r-0 border-line-divider p-0'>
             <div
-              className='flex cursor-pointer items-center px-4 py-2 text-text-caption hover:bg-fill-hover hover:text-text-title'
+              className='flex cursor-pointer items-center px-4 py-2 text-text-caption hover:bg-fill-list-hover hover:text-text-title'
               onClick={onAddField}
             >
               <i className='mr-2 h-5 w-5'>
                 <AddSvg />
               </i>
-              <span>{t('grid.newCol')}</span>
+              <span>{t('grid.field.newProperty')}</span>
             </div>
           </th>
         </tr>

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeaderItem.tsx

@@ -61,9 +61,9 @@ export const GridTableHeaderItem = ({
   };
 
   return (
-    <th key={field.fieldId} className='m-0 border border-l-0 border-line-border p-0'>
+    <th key={field.fieldId} className='m-0 border border-l-0 border-line-divider p-0'>
       <div
-        className={'flex w-full cursor-pointer items-center px-4 py-2 hover:bg-fill-hover'}
+        className={'flex w-full cursor-pointer items-center px-4 py-2 hover:bg-fill-list-hover'}
         ref={ref}
         onClick={() => {
           if (!ref.current) return;

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRow.tsx

@@ -21,7 +21,7 @@ export const GridTableRow = ({
     <tr className='group'>
       {cells.map((cell, cellIndex) => {
         return (
-          <td className='m-0  border border-l-0 border-line-border p-0 ' key={cellIndex}>
+          <td className='m-0  border border-l-0 border-line-divider p-0 ' key={cellIndex}>
             <div className='flex w-full items-center justify-end'>
               <GridCell
                 cellIdentifier={cell.cellIdentifier}
@@ -32,7 +32,7 @@ export const GridTableRow = ({
               {cellIndex === 0 && (
                 <div
                   onClick={() => onOpenRow(row)}
-                  className='mr-1 hidden h-8 w-8 cursor-pointer rounded p-1.5 text-text-caption hover:bg-fill-hover group-hover:block '
+                  className='mr-1 hidden h-8 w-8 cursor-pointer rounded p-1.5 text-text-caption hover:bg-fill-list-hover group-hover:block '
                 >
                   <FullView />
                 </div>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/FooterPanel.tsx

@@ -5,7 +5,7 @@ export const FooterPanel = () => {
         &copy; 2023 AppFlowy. <a href={'https://github.com/AppFlowy-IO/AppFlowy'}>GitHub</a>
       </div>
       <div>
-        <button className={'h-8 w-8 rounded bg-fill-selector text-text-title hover:bg-fill-hover'}>?</button>
+        <button className={'h-8 w-8 rounded bg-content-blue-50 text-text-title hover:bg-content-blue-100'}>?</button>
       </div>
     </div>
   );

+ 8 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx

@@ -38,10 +38,16 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole
           </button>
         )}
 
-        <button className={'h-6 w-6 rounded p-1 text-text-title hover:bg-fill-hover'} onClick={() => history.back()}>
+        <button
+          className={'h-6 w-6 rounded p-1 text-text-title hover:bg-fill-list-hover'}
+          onClick={() => history.back()}
+        >
           <ArrowLeftSvg />
         </button>
-        <button className={'h-6 w-6 rounded p-1 text-text-title hover:bg-fill-hover'} onClick={() => history.forward()}>
+        <button
+          className={'h-6 w-6 rounded p-1 text-text-title hover:bg-fill-list-hover'}
+          onClick={() => history.forward()}
+        >
           <ArrowRightSvg />
         </button>
       </div>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx

@@ -3,7 +3,7 @@ import { PageOptions } from './PageOptions';
 
 export const HeaderPanel = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
   return (
-    <div className={'flex h-[60px] items-center justify-between border-b border-line-border px-8'}>
+    <div className={'flex h-[60px] items-center justify-between border-b border-line-divider px-8'}>
       <Breadcrumbs menuHidden={menuHidden} onShowMenuClick={onShowMenuClick}></Breadcrumbs>
       <PageOptions></PageOptions>
     </div>

+ 0 - 16
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx

@@ -1,16 +0,0 @@
-import { useState } from 'react';
-import { LanguageSelectPopup } from '$app/components/_shared/LanguageSelectPopup';
-import { LanguageOutlined } from '@mui/icons-material';
-
-export const LanguageButton = () => {
-  const [showPopup, setShowPopup] = useState(false);
-
-  return (
-    <>
-      <button onClick={() => setShowPopup(!showPopup)} className={'h-8 w-8 rounded text-text-title hover:bg-fill-hover'}>
-        <LanguageOutlined />
-      </button>
-      {showPopup && <LanguageSelectPopup onClose={() => setShowPopup(false)}></LanguageSelectPopup>}
-    </>
-  );
-};

+ 45 - 0
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx

@@ -0,0 +1,45 @@
+import React, { useCallback, useMemo } from 'react';
+import { LogoutSvg } from '$app/components/_shared/svg/LogoutSvg';
+import { useAuth } from '$app/components/auth/auth.hooks';
+import MenuItem from '@mui/material/MenuItem';
+import { useTranslation } from 'react-i18next';
+
+function MoreMenu({ onClose }: { onClose: () => void }) {
+  const { t } = useTranslation();
+  const { logout } = useAuth();
+  const onSignOutClick = useCallback(async () => {
+    await logout();
+    onClose();
+  }, [onClose, logout]);
+
+  const items = useMemo(() => {
+    return [
+      {
+        title: t('button.signOut'),
+        icon: (
+          <i className={'block h-5 w-5 flex-shrink-0'}>
+            <LogoutSvg></LogoutSvg>
+          </i>
+        ),
+        onClick: onSignOutClick,
+      },
+    ];
+  }, [onSignOutClick, t]);
+
+  return (
+    <>
+      {items.map((item, index) => {
+        return (
+          <MenuItem key={index} onClick={item.onClick}>
+            <div className={'flex items-center gap-2'}>
+              {item.icon}
+              <span className={'flex-shrink-0'}>{item.title}</span>
+            </div>
+          </MenuItem>
+        );
+      })}
+    </>
+  );
+}
+
+export default MoreMenu;

+ 0 - 23
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx

@@ -1,23 +0,0 @@
-import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
-import { LogoutSvg } from '../../_shared/svg/LogoutSvg';
-
-export const OptionsPopup = ({ onSignOutClick, onClose }: { onSignOutClick: () => void; onClose: () => void }) => {
-  const items: IPopupItem[] = [
-    {
-      title: 'Sign out',
-      icon: (
-        <i className={'block h-5 w-5 flex-shrink-0'}>
-          <LogoutSvg></LogoutSvg>
-        </i>
-      ),
-      onClick: onSignOutClick,
-    },
-  ];
-  return (
-    <PopupSelect
-      className={'absolute top-[50px] right-[30px] z-10 whitespace-nowrap'}
-      items={items}
-      onOutsideClick={onClose}
-    ></PopupSelect>
-  );
-};

+ 7 - 14
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts

@@ -1,27 +1,20 @@
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
 import { useAuth } from '../../auth/auth.hooks';
 
 export const usePageOptions = () => {
-  const [showOptionsPopup, setShowOptionsPopup] = useState(false);
-  const { logout } = useAuth();
+  const [anchorEl, setAnchorEl] = useState<HTMLDivElement | HTMLButtonElement>();
 
-  const onOptionsClick = () => {
-    setShowOptionsPopup(true);
-  };
+  const onOptionsClick = useCallback((el: HTMLDivElement | HTMLButtonElement) => {
+    setAnchorEl(el);
+  }, []);
 
   const onClose = () => {
-    setShowOptionsPopup(false);
-  };
-
-  const onSignOutClick = async () => {
-    await logout();
-    onClose();
+    setAnchorEl(undefined);
   };
 
   return {
-    showOptionsPopup,
+    anchorEl,
     onOptionsClick,
     onClose,
-    onSignOutClick,
   };
 };

+ 56 - 13
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx

@@ -1,30 +1,73 @@
-import { Button } from '../../_shared/Button';
 import { Details2Svg } from '../../_shared/svg/Details2Svg';
 import { usePageOptions } from './PageOptions.hooks';
-import { OptionsPopup } from './OptionsPopup';
-import { LanguageButton } from '$app/components/layout/HeaderPanel/LanguageButton';
+import { Button, IconButton, List } from '@mui/material';
+import Popover from '@mui/material/Popover';
+import { useCallback, useState } from 'react';
+import MoreMenu from '$app/components/layout/HeaderPanel/MoreMenu';
+import { useTranslation } from 'react-i18next';
 
+enum PageOptionsEnum {
+  Share = 'Share',
+  More = 'More',
+}
 export const PageOptions = () => {
-  const { showOptionsPopup, onOptionsClick, onClose, onSignOutClick } = usePageOptions();
+  const { t } = useTranslation();
+  const { anchorEl, onOptionsClick, onClose } = usePageOptions();
+  const open = Boolean(anchorEl);
+  const [option, setOption] = useState<PageOptionsEnum>();
+  const renderMenu = useCallback(() => {
+    switch (option) {
+      case PageOptionsEnum.Share:
+        return <div>Share</div>;
+      default:
+        return <MoreMenu onClose={onClose} />;
+    }
+  }, [onClose, option]);
 
   return (
     <>
       <div className={'relative flex items-center gap-4'}>
-        <Button size={'small'} onClick={() => console.log('share click')}>
-          Share
-        </Button>
+        <Button
+          variant={'contained'}
+          onClick={(e) => {
+            const el = e.currentTarget;
 
-        <LanguageButton></LanguageButton>
+            setOption(PageOptionsEnum.Share);
+            onOptionsClick(el);
+          }}
+        >
+          {t('shareAction.buttonText')}
+        </Button>
 
-        <button
+        <IconButton
           id='option-button'
-          className={'relative h-8 w-8 rounded text-text-title hover:bg-fill-hover'}
-          onClick={onOptionsClick}
+          size={'small'}
+          className={'h-8 w-8 rounded text-text-title hover:bg-fill-list-hover'}
+          onClick={(e) => {
+            const el = e.currentTarget;
+
+            setOption(PageOptionsEnum.More);
+            onOptionsClick(el);
+          }}
         >
           <Details2Svg></Details2Svg>
-        </button>
+        </IconButton>
       </div>
-      {showOptionsPopup && <OptionsPopup onSignOutClick={onSignOutClick} onClose={onClose}></OptionsPopup>}
+      <Popover
+        open={open}
+        anchorEl={anchorEl}
+        onClose={onClose}
+        anchorOrigin={{
+          vertical: 'bottom',
+          horizontal: 'right',
+        }}
+        transformOrigin={{
+          vertical: 'top',
+          horizontal: 'right',
+        }}
+      >
+        <List>{renderMenu()}</List>
+      </Popover>
     </>
   );
 };

+ 84 - 0
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx

@@ -0,0 +1,84 @@
+import React, { useMemo, useState } from 'react';
+import { EditSvg } from '$app/components/_shared/svg/EditSvg';
+import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
+import { CopySvg } from '$app/components/_shared/svg/CopySvg';
+import MenuItem from '@mui/material/MenuItem';
+import { useTranslation } from 'react-i18next';
+import RenameDialog from '$app/components/layout/NavigationPanel/RenameDialog';
+import { IPage } from '$app_reducers/pages/slice';
+
+function MoreMenu({
+  selectedPage,
+  onRename,
+  onDeleteClick,
+  onDuplicateClick,
+}: {
+  selectedPage: IPage;
+  onRename: (name: string) => Promise<void>;
+  onDeleteClick: () => void;
+  onDuplicateClick: () => void;
+}) {
+  const { t } = useTranslation();
+  const [renameDialogOpen, setRenameDialogOpen] = useState(false);
+
+  const items = useMemo(
+    () => [
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <EditSvg></EditSvg>
+          </i>
+        ),
+        onClick: () => {
+          setRenameDialogOpen(true);
+        },
+        title: t('disclosureAction.rename'),
+      },
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <TrashSvg></TrashSvg>
+          </i>
+        ),
+        onClick: onDeleteClick,
+        title: t('disclosureAction.delete'),
+      },
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <CopySvg></CopySvg>
+          </i>
+        ),
+        onClick: onDuplicateClick,
+        title: t('disclosureAction.duplicate'),
+      },
+    ],
+    [onDeleteClick, onDuplicateClick, t]
+  );
+
+  return (
+    <>
+      {items.map((item, index) => {
+        return (
+          <MenuItem key={index} onClick={item.onClick}>
+            <div className={'flex items-center gap-2'}>
+              {item.icon}
+              <span className={'flex-shrink-0'}>{item.title}</span>
+            </div>
+          </MenuItem>
+        );
+      })}
+      <RenameDialog
+        defaultValue={selectedPage.title}
+        open={renameDialogOpen}
+        onClose={() => setRenameDialogOpen(false)}
+        onOk={async (val: string) => {
+          await onRename(val);
+          setRenameDialogOpen(false);
+        }}
+      />
+    </>
+  );
+}
+
+export default MoreMenu;

+ 23 - 54
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts

@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 import { useAppDispatch, useAppSelector } from '$app/stores/store';
 import { IPage, pagesActions } from '$app_reducers/pages/slice';
 import { ViewLayoutPB } from '@/services/backend';
@@ -10,23 +10,26 @@ import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants
 import { ViewBackendService } from '$app/stores/effects/folder/view/view_bd_svc';
 import { ViewObserver } from '$app/stores/effects/folder/view/view_observer';
 
+export enum NavItemOptions {
+  More = 'More',
+  NewPage = 'NewPage',
+}
 export const useNavItem = (page: IPage) => {
   const appDispatch = useAppDispatch();
   const workspace = useAppSelector((state) => state.workspace);
   const currentLocation = useLocation();
   const [activePageId, setActivePageId] = useState<string>('');
   const pages = useAppSelector((state) => state.pages);
-
+  const [anchorEl, setAnchorEl] = useState<HTMLElement>();
+  const menuOpen = Boolean(anchorEl);
+  const [menuOption, setMenuOption] = useState<NavItemOptions>();
+  const [selectedPage, setSelectedPage] = useState<IPage>();
+  const onClickMenuBtn = useCallback((page: IPage, option: NavItemOptions) => {
+    setSelectedPage(page);
+    setMenuOption(option);
+  }, []);
   const navigate = useNavigate();
 
-  // Actions
-  const [showPageOptions, setShowPageOptions] = useState(false);
-  const [showNewPageOptions, setShowNewPageOptions] = useState(false);
-  const [showRenamePopup, setShowRenamePopup] = useState(false);
-
-  // UI configurations
-  const [folderHeight, setFolderHeight] = useState(`${INITIAL_FOLDER_HEIGHT}px`);
-
   // backend
   const service = new ViewBackendService(page.id);
   const observer = new ViewObserver(page.id);
@@ -68,14 +71,6 @@ export const useNavItem = (page: IPage) => {
     setActivePageId(pageId);
   }, [currentLocation]);
 
-  useEffect(() => {
-    if (page.showPagesInside) {
-      setFolderHeight(`${PAGE_ITEM_HEIGHT + getChildCount(page) * PAGE_ITEM_HEIGHT}px`);
-    } else {
-      setFolderHeight(`${PAGE_ITEM_HEIGHT}px`);
-    }
-  }, [page, pages]);
-
   // recursively get all unfolded child pages
   const getChildCount: (startPage: IPage) => number = (startPage: IPage) => {
     let count = 0;
@@ -95,42 +90,21 @@ export const useNavItem = (page: IPage) => {
     appDispatch(pagesActions.toggleShowPages({ id: page.id }));
   };
 
-  const onPageOptionsClick = () => {
-    setShowPageOptions((prevState) => !prevState);
-  };
-
-  const startPageRename = () => {
-    setShowRenamePopup(true);
-    closePopup();
-  };
-
-  const onNewPageClick = () => {
-    setShowNewPageOptions(!showNewPageOptions);
-  };
-
   const changePageTitle = async (newTitle: string) => {
     await service.update({ name: newTitle });
     appDispatch(pagesActions.renamePage({ id: page.id, newTitle }));
-  };
-
-  const closeRenamePopup = () => {
-    setShowRenamePopup(false);
+    setAnchorEl(undefined);
   };
 
   const deletePage = async () => {
-    closePopup();
     await service.delete();
     appDispatch(pagesActions.deletePage({ id: page.id }));
+    setAnchorEl(undefined);
   };
 
   const duplicatePage = async () => {
-    closePopup();
     await service.duplicate();
-  };
-
-  const closePopup = () => {
-    setShowPageOptions(false);
-    setShowNewPageOptions(false);
+    setAnchorEl(undefined);
   };
 
   const onPageClick = (eventPage: IPage) => {
@@ -151,7 +125,6 @@ export const useNavItem = (page: IPage) => {
   };
 
   const onAddNewPage = async (pageType: ViewLayoutPB) => {
-    closePopup();
     if (!workspace?.id) return;
 
     let newPageName = '';
@@ -199,24 +172,15 @@ export const useNavItem = (page: IPage) => {
           showPagesInside: false,
         })
       );
-
+      setAnchorEl(undefined);
       navigate(`/page/${pageTypeRoute}/${newView.id}`);
     }
   };
 
   return {
     onUnfoldClick,
-    onNewPageClick,
-    onPageOptionsClick,
-    startPageRename,
 
     changePageTitle,
-    closeRenamePopup,
-    closePopup,
-
-    showNewPageOptions,
-    showPageOptions,
-    showRenamePopup,
 
     deletePage,
     duplicatePage,
@@ -225,7 +189,12 @@ export const useNavItem = (page: IPage) => {
 
     onAddNewPage,
 
-    folderHeight,
     activePageId,
+    menuOpen,
+    anchorEl,
+    setAnchorEl,
+    menuOption,
+    selectedPage,
+    onClickMenuBtn,
   };
 };

+ 92 - 91
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx

@@ -1,123 +1,124 @@
 import { Details2Svg } from '../../_shared/svg/Details2Svg';
 import AddSvg from '../../_shared/svg/AddSvg';
-import { NavItemOptionsPopup } from './NavItemOptionsPopup';
-import { NewPagePopup } from './NewPagePopup';
 import { IPage } from '$app_reducers/pages/slice';
-import { Button } from '../../_shared/Button';
-import { RenamePopup } from './RenamePopup';
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useMemo, useRef } from 'react';
 import { DropDownShowSvg } from '../../_shared/svg/DropDownShowSvg';
-import { ANIMATION_DURATION, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
-import { useNavItem } from '$app/components/layout/NavigationPanel/NavItem.hooks';
+import { ANIMATION_DURATION } from '../../_shared/constants';
+import { NavItemOptions, useNavItem } from '$app/components/layout/NavigationPanel/NavItem.hooks';
 import { useAppSelector } from '$app/stores/store';
 import { ViewLayoutPB } from '@/services/backend';
+import Popover from '@mui/material/Popover';
+import { IconButton, List } from '@mui/material';
+import MoreMenu from '$app/components/layout/NavigationPanel/MoreMenu';
+import NewPageMenu from '$app/components/layout/NavigationPanel/NewPageMenu';
 
 export const NavItem = ({ page }: { page: IPage }) => {
   const pages = useAppSelector((state) => state.pages);
   const {
     onUnfoldClick,
-    onNewPageClick,
-    onPageOptionsClick,
-    startPageRename,
-
     changePageTitle,
-    closeRenamePopup,
-    closePopup,
-
-    showNewPageOptions,
-    showPageOptions,
-    showRenamePopup,
-
     deletePage,
     duplicatePage,
 
     onAddNewPage,
 
-    folderHeight,
     activePageId,
 
     onPageClick,
+    onClickMenuBtn,
+    menuOpen,
+    menuOption,
+    setAnchorEl,
+    selectedPage,
+    anchorEl,
   } = useNavItem(page);
 
-  const [popupY, setPopupY] = useState(0);
-
   const el = useRef<HTMLDivElement>(null);
 
-  useEffect(() => {
-    if (el.current) {
-      const { top } = el.current.getBoundingClientRect();
-
-      setPopupY(top);
-    }
-  }, [showPageOptions, showNewPageOptions, showRenamePopup]);
-
   return (
-    <div ref={el}>
-      <div
-        className={`overflow-hidden transition-all`}
-        style={{ height: folderHeight, transitionDuration: `${ANIMATION_DURATION}ms` }}
-      >
-        <div style={{ height: PAGE_ITEM_HEIGHT }} className={`cursor-pointer px-1 py-1`}>
-          <div
-            className={`flex items-center justify-between rounded-lg px-2 py-1 hover:bg-fill-active ${
-              activePageId === page.id ? 'bg-fill-active' : ''
-            }`}
-          >
-            <div className={'flex h-full min-w-0 flex-1 items-center'}>
-              <button
-                onClick={() => onUnfoldClick()}
-                className={`mr-2 h-5 w-5 transition-transform duration-200 ${page.showPagesInside && 'rotate-180'}`}
-              >
-                <DropDownShowSvg></DropDownShowSvg>
-              </button>
-              <div onClick={() => onPageClick(page)} className={'mr-1 flex h-full min-w-0 items-center text-left'}>
-                <span className={'w-[100%] overflow-hidden overflow-ellipsis whitespace-nowrap'}>{page.title}</span>
+    <>
+      <div ref={el}>
+        <div className={`transition-all`} style={{ transitionDuration: `${ANIMATION_DURATION}ms` }}>
+          <div className={`cursor-pointer px-1 py-1`}>
+            <div
+              className={`flex items-center justify-between rounded-lg px-2 py-1 hover:bg-fill-list-hover ${
+                activePageId === page.id ? 'bg-fill-list-hover' : ''
+              }`}
+            >
+              <div className={'flex h-full min-w-0 flex-1 items-center'}>
+                <button
+                  onClick={() => onUnfoldClick()}
+                  className={`mr-2 h-5 w-5 transition-transform duration-200 ${
+                    page.showPagesInside ? 'rotate-180' : ''
+                  }`}
+                >
+                  <DropDownShowSvg></DropDownShowSvg>
+                </button>
+                <div
+                  onClick={() => onPageClick(page)}
+                  className={'mr-1 flex h-full min-w-0 flex-1 items-center text-left'}
+                >
+                  <span className={'w-[100%] overflow-hidden overflow-ellipsis whitespace-nowrap'}>{page.title}</span>
+                </div>
+              </div>
+              <div className={'flex items-center'}>
+                <IconButton
+                  className={'h-6 w-6'}
+                  size={'small'}
+                  onClick={(e) => {
+                    setAnchorEl(e.currentTarget);
+                    onClickMenuBtn(page, NavItemOptions.More);
+                  }}
+                >
+                  <Details2Svg></Details2Svg>
+                </IconButton>
+                <IconButton
+                  className={'h-6 w-6'}
+                  size={'small'}
+                  onClick={(e) => {
+                    setAnchorEl(e.currentTarget);
+                    onClickMenuBtn(page, NavItemOptions.NewPage);
+                  }}
+                >
+                  <AddSvg></AddSvg>
+                </IconButton>
               </div>
-            </div>
-            <div className={'flex items-center'}>
-              <Button size={'box-small-transparent'} onClick={() => onPageOptionsClick()}>
-                <Details2Svg></Details2Svg>
-              </Button>
-              <Button size={'box-small-transparent'} onClick={() => onNewPageClick()}>
-                <AddSvg></AddSvg>
-              </Button>
             </div>
           </div>
-        </div>
-        <div className={'pl-4'}>
-          {useMemo(() => pages.filter((insidePage) => insidePage.parentPageId === page.id), [pages, page]).map(
-            (insidePage, insideIndex) => (
-              <NavItem key={insideIndex} page={insidePage}></NavItem>
-            )
-          )}
+          <div className={`${page.showPagesInside ? '' : 'hidden'} pl-4`}>
+            {useMemo(() => pages.filter((insidePage) => insidePage.parentPageId === page.id), [pages, page]).map(
+              (insidePage, insideIndex) => (
+                <NavItem key={insideIndex} page={insidePage}></NavItem>
+              )
+            )}
+          </div>
         </div>
       </div>
-      {showPageOptions && (
-        <NavItemOptionsPopup
-          onRenameClick={() => startPageRename()}
-          onDeleteClick={() => deletePage()}
-          onDuplicateClick={() => duplicatePage()}
-          onClose={() => closePopup()}
-          top={popupY - 124 + 58}
-        ></NavItemOptionsPopup>
-      )}
-      {showNewPageOptions && (
-        <NewPagePopup
-          onDocumentClick={() => onAddNewPage(ViewLayoutPB.Document)}
-          onBoardClick={() => onAddNewPage(ViewLayoutPB.Board)}
-          onGridClick={() => onAddNewPage(ViewLayoutPB.Grid)}
-          onClose={() => closePopup()}
-          top={popupY - 124 + 58}
-        ></NewPagePopup>
-      )}
-      {showRenamePopup && (
-        <RenamePopup
-          value={page.title}
-          onChange={(newTitle) => changePageTitle(newTitle)}
-          onClose={closeRenamePopup}
-          top={popupY - 124 + 40}
-        ></RenamePopup>
-      )}
-    </div>
+      <Popover
+        open={menuOpen}
+        anchorEl={anchorEl}
+        onClose={() => setAnchorEl(undefined)}
+        anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
+        transformOrigin={{ vertical: 'top', horizontal: 'left' }}
+      >
+        <List>
+          {menuOption === NavItemOptions.More && selectedPage && (
+            <MoreMenu
+              selectedPage={selectedPage}
+              onRename={changePageTitle}
+              onDeleteClick={() => deletePage()}
+              onDuplicateClick={() => duplicatePage()}
+            />
+          )}
+          {menuOption === NavItemOptions.NewPage && (
+            <NewPageMenu
+              onDocumentClick={() => onAddNewPage(ViewLayoutPB.Document)}
+              onBoardClick={() => onAddNewPage(ViewLayoutPB.Board)}
+              onGridClick={() => onAddNewPage(ViewLayoutPB.Grid)}
+            />
+          )}
+        </List>
+      </Popover>
+    </>
   );
 };

+ 0 - 57
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx

@@ -1,57 +0,0 @@
-import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
-import { EditSvg } from '../../_shared/svg/EditSvg';
-import { TrashSvg } from '../../_shared/svg/TrashSvg';
-import { CopySvg } from '../../_shared/svg/CopySvg';
-
-export const NavItemOptionsPopup = ({
-  onRenameClick,
-  onDeleteClick,
-  onDuplicateClick,
-  onClose,
-  top,
-}: {
-  onRenameClick: () => void;
-  onDeleteClick: () => void;
-  onDuplicateClick: () => void;
-  onClose?: () => void;
-  top: number;
-}) => {
-  const items: IPopupItem[] = [
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <EditSvg></EditSvg>
-        </i>
-      ),
-      onClick: onRenameClick,
-      title: 'Rename',
-    },
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <TrashSvg></TrashSvg>
-        </i>
-      ),
-      onClick: onDeleteClick,
-      title: 'Delete',
-    },
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <CopySvg></CopySvg>
-        </i>
-      ),
-      onClick: onDuplicateClick,
-      title: 'Duplicate',
-    },
-  ];
-
-  return (
-    <PopupSelect
-      onOutsideClick={() => onClose && onClose()}
-      items={items}
-      className={`absolute right-0`}
-      style={{ top: `${top}px` }}
-    ></PopupSelect>
-  );
-};

+ 10 - 18
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx

@@ -54,24 +54,16 @@ export const NavigationPanel = ({
           left: `${menuHidden ? -width : 0}px`,
         }}
       >
-        <div className={'flex flex-col'}>
-          <AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
-          <WorkspaceUser></WorkspaceUser>
-          <div className={'relative flex flex-1 flex-col'}>
-            <div
-              className={'flex flex-col overflow-auto px-2'}
-              style={{
-                maxHeight: 'calc(100vh - 350px)',
-              }}
-              ref={el}
-            >
-              <WorkspaceApps pages={pages.filter((p) => p.parentPageId === workspace.id)} />
-            </div>
+        <AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
+        <WorkspaceUser></WorkspaceUser>
+        <div className={'relative flex flex-1 flex-col'}>
+          <div className={'flex h-[100%] flex-col overflow-auto px-2'} ref={el}>
+            <WorkspaceApps pages={pages.filter((p) => p.parentPageId === workspace.id)} />
           </div>
         </div>
 
-        <div className={'flex max-h-[215px] flex-col'}>
-          <div className={'border-b border-line-border px-2 pb-4'}>
+        <div className={'flex max-h-[240px] flex-col'}>
+          <div className={'border-b border-line-divider px-2 pb-4'}>
             {/*<PluginsButton></PluginsButton>*/}
 
             {/*<DesignSpec></DesignSpec>*/}
@@ -105,7 +97,7 @@ export const TestBackendButton = () => {
   return (
     <button
       onClick={() => navigate('/page/api-test')}
-      className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-fill-active'}
+      className={'hover:bg-fill-active flex w-full items-center rounded-lg px-4 py-2'}
     >
       API Test
     </button>
@@ -118,7 +110,7 @@ export const DesignSpec = () => {
   return (
     <button
       onClick={() => navigate('page/colors')}
-      className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-fill-active'}
+      className={'hover:bg-fill-active flex w-full items-center rounded-lg px-4 py-2'}
     >
       Color Palette
     </button>
@@ -131,7 +123,7 @@ export const AllIcons = () => {
   return (
     <button
       onClick={() => navigate('page/all-icons')}
-      className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-fill-active'}
+      className={'hover:bg-fill-active flex w-full items-center rounded-lg px-4 py-2'}
     >
       All Icons
     </button>

+ 67 - 0
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx

@@ -0,0 +1,67 @@
+import React, { useMemo } from 'react';
+import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg';
+import { BoardSvg } from '$app/components/_shared/svg/BoardSvg';
+import { GridSvg } from '$app/components/_shared/svg/GridSvg';
+import MenuItem from '@mui/material/MenuItem';
+import { useTranslation } from 'react-i18next';
+
+function NewPageMenu({
+  onDocumentClick,
+  onGridClick,
+  onBoardClick,
+}: {
+  onDocumentClick: () => void;
+  onGridClick: () => void;
+  onBoardClick: () => void;
+}) {
+  const { t } = useTranslation();
+  const items = useMemo(
+    () => [
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <DocumentSvg></DocumentSvg>
+          </i>
+        ),
+        onClick: onDocumentClick,
+        title: t('document.menuName'),
+      },
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <BoardSvg></BoardSvg>
+          </i>
+        ),
+        onClick: onBoardClick,
+        title: t('board.menuName'),
+      },
+      {
+        icon: (
+          <i className={'h-[16px] w-[16px] text-text-title'}>
+            <GridSvg></GridSvg>
+          </i>
+        ),
+        onClick: onGridClick,
+        title: t('grid.menuName'),
+      },
+    ],
+    [onBoardClick, onDocumentClick, onGridClick, t]
+  );
+
+  return (
+    <>
+      {items.map((item, index) => {
+        return (
+          <MenuItem key={index} onClick={item.onClick}>
+            <div className={'flex items-center gap-2'}>
+              {item.icon}
+              <span className={'flex-shrink-0'}>{item.title}</span>
+            </div>
+          </MenuItem>
+        );
+      })}
+    </>
+  );
+}
+
+export default NewPageMenu;

+ 0 - 57
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx

@@ -1,57 +0,0 @@
-import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
-import { DocumentSvg } from '../../_shared/svg/DocumentSvg';
-import { BoardSvg } from '../../_shared/svg/BoardSvg';
-import { GridSvg } from '../../_shared/svg/GridSvg';
-
-export const NewPagePopup = ({
-  onDocumentClick,
-  onGridClick,
-  onBoardClick,
-  onClose,
-  top,
-}: {
-  onDocumentClick: () => void;
-  onGridClick: () => void;
-  onBoardClick: () => void;
-  onClose?: () => void;
-  top: number;
-}) => {
-  const items: IPopupItem[] = [
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <DocumentSvg></DocumentSvg>
-        </i>
-      ),
-      onClick: onDocumentClick,
-      title: 'Document',
-    },
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <BoardSvg></BoardSvg>
-        </i>
-      ),
-      onClick: onBoardClick,
-      title: 'Board',
-    },
-    {
-      icon: (
-        <i className={'h-[16px] w-[16px] text-text-title'}>
-          <GridSvg></GridSvg>
-        </i>
-      ),
-      onClick: onGridClick,
-      title: 'Grid',
-    },
-  ];
-
-  return (
-    <PopupSelect
-      onOutsideClick={() => onClose && onClose()}
-      items={items}
-      className={'absolute right-0'}
-      style={{ top: `${top}px` }}
-    ></PopupSelect>
-  );
-};

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx

@@ -10,10 +10,10 @@ export const NewViewButton = ({ scrollDown }: { scrollDown: () => void }) => {
         void onNewRootView();
         scrollDown();
       }}
-      className={'flex h-[50px] w-full items-center px-6 hover:bg-fill-active'}
+      className={'flex h-[50px] w-full items-center px-6 hover:bg-fill-list-active'}
     >
       <div className={'mr-2 rounded-full bg-fill-default'}>
-        <div className={'h-[24px] w-[24px] text-content-onfill'}>
+        <div className={'h-[24px] w-[24px] text-content-on-fill'}>
           <AddSvg></AddSvg>
         </div>
       </div>

+ 52 - 0
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx

@@ -0,0 +1,52 @@
+import React, { useState } from 'react';
+import DialogTitle from '@mui/material/DialogTitle';
+import DialogContent from '@mui/material/DialogContent';
+import Dialog from '@mui/material/Dialog';
+import { useTranslation } from 'react-i18next';
+import TextField from '@mui/material/TextField';
+import { Button, DialogActions } from '@mui/material';
+
+function RenameDialog({
+  defaultValue,
+  open,
+  onClose,
+  onOk,
+}: {
+  defaultValue: string;
+  open: boolean;
+  onClose: () => void;
+  onOk: (val: string) => void;
+}) {
+  const { t } = useTranslation();
+  const [value, setValue] = useState(defaultValue);
+
+  return (
+    <Dialog keepMounted={false} onMouseDown={(e) => e.stopPropagation()} open={open} onClose={onClose}>
+      <DialogTitle>{t('menuAppHeader.renameDialog')}</DialogTitle>
+      <DialogContent className={'flex w-[540px]'}>
+        <TextField
+          autoFocus
+          value={value}
+          onChange={(e) => {
+            setValue(e.target.value);
+          }}
+          margin='dense'
+          fullWidth
+          variant='standard'
+        />
+      </DialogContent>
+      <DialogActions>
+        <Button onClick={onClose}>{t('button.Cancel')}</Button>
+        <Button
+          onClick={() => {
+            onOk(value);
+          }}
+        >
+          {t('button.OK')}
+        </Button>
+      </DialogActions>
+    </Dialog>
+  );
+}
+
+export default RenameDialog;

+ 0 - 47
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx

@@ -1,47 +0,0 @@
-import { useEffect, useRef } from 'react';
-import useOutsideClick from '../../_shared/useOutsideClick';
-
-export const RenamePopup = ({
-  value,
-  onChange,
-  onClose,
-  className = '',
-  top,
-}: {
-  value: string;
-  onChange: (newTitle: string) => void;
-  onClose: () => void;
-  className?: string;
-  top?: number;
-}) => {
-  const ref = useRef<HTMLDivElement>(null);
-  const inputRef = useRef<HTMLInputElement>(null);
-  useOutsideClick(ref, () => onClose && onClose());
-
-  useEffect(() => {
-    if (!inputRef || !inputRef.current) return;
-
-    const { current: el } = inputRef;
-
-    el.focus();
-    el.selectionStart = 0;
-    el.selectionEnd = el.value.length;
-  }, [inputRef]);
-
-  return (
-    <div
-      ref={ref}
-      className={
-        'absolute left-[50px] top-[40px] z-10 flex w-[300px] rounded bg-white py-1 px-1.5 shadow-md ' + className
-      }
-      style={{ top: `${top}px` }}
-    >
-      <input
-        ref={inputRef}
-        className={'border-shades-3 flex-1 rounded border bg-main-selector p-1'}
-        value={value}
-        onChange={(e) => onChange(e.target.value)}
-      />
-    </div>
-  );
-};

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx

@@ -3,7 +3,7 @@ import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
 
 export const TrashButton = () => {
   return (
-    <button className={'flex w-full items-center rounded-lg px-4 py-2 text-text-title hover:bg-fill-active'}>
+    <button className={'flex w-full items-center rounded-lg px-4 py-2 text-text-title hover:bg-fill-list-active'}>
       <span className={'h-[23px] w-[23px]'}>
         <TrashSvg />
       </span>

+ 8 - 5
frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/AppearanceSetting.tsx

@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
 import Select from '@mui/material/Select';
 import { Theme, ThemeMode, UserSetting } from '$app/interfaces';
 import MenuItem from '@mui/material/MenuItem';
+import { useTranslation } from 'react-i18next';
 
 function AppearanceSetting({
   theme = Theme.Default,
@@ -12,6 +13,8 @@ function AppearanceSetting({
   themeMode?: ThemeMode;
   onChange: (setting: UserSetting) => void;
 }) {
+  const { t } = useTranslation();
+
   useEffect(() => {
     const html = document.documentElement;
 
@@ -23,14 +26,14 @@ function AppearanceSetting({
     () => [
       {
         value: ThemeMode.Light,
-        content: 'Light',
+        content: t('settings.appearance.themeMode.light'),
       },
       {
         value: ThemeMode.Dark,
-        content: 'Dark',
+        content: t('settings.appearance.themeMode.dark'),
       },
     ],
-    []
+    [t]
   );
 
   const themeOptions = useMemo(
@@ -96,7 +99,7 @@ function AppearanceSetting({
       {renderSelect([
         {
           options: themeModeOptions,
-          label: 'Theme Mode',
+          label: t('settings.appearance.themeMode.label'),
           value: themeMode,
           onChange: (newValue) => {
             onChange({
@@ -106,7 +109,7 @@ function AppearanceSetting({
         },
         {
           options: themeOptions,
-          label: 'Theme',
+          label: t('settings.appearance.theme'),
           value: theme,
           onChange: (newValue) => {
             onChange({

+ 69 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/LanguageSetting.tsx

@@ -1,7 +1,74 @@
 import React from 'react';
+import { useTranslation } from 'react-i18next';
+import Select from '@mui/material/Select';
+import { UserSetting } from '$app/interfaces';
+import MenuItem from '@mui/material/MenuItem';
 
-function LanguageSetting() {
-  return <div></div>;
+const languages = [
+  {
+    key: 'ar-SA',
+    title: 'العربية',
+  },
+  { key: 'ca-ES', title: 'Català' },
+  { key: 'de-DE', title: 'Deutsch' },
+  { key: 'en', title: 'English' },
+  { key: 'es-VE', title: 'Español (Venezuela)' },
+  { key: 'eu-ES', title: 'Español' },
+  { key: 'fr-FR', title: 'Français' },
+  { key: 'hu-HU', title: 'Magyar' },
+  { key: 'id-ID', title: 'Bahasa Indonesia' },
+  { key: 'it-IT', title: 'Italiano' },
+  { key: 'ja-JP', title: '日本語' },
+  { key: 'ko-KR', title: '한국어' },
+  { key: 'pl-PL', title: 'Polski' },
+  { key: 'pt-BR', title: 'Português' },
+  { key: 'pt-PT', title: 'Português' },
+  { key: 'ru-RU', title: 'Русский' },
+  { key: 'sv', title: 'Svenska' },
+  { key: 'tr-TR', title: 'Türkçe' },
+  { key: 'zh-CN', title: '简体中文' },
+  { key: 'zh-TW', title: '繁體中文' },
+];
+
+function LanguageSetting({
+  language = 'en',
+  onChange,
+}: {
+  language?: string;
+  onChange: (setting: UserSetting) => void;
+}) {
+  const { t, i18n } = useTranslation();
+
+  return (
+    <div className={'flex flex-col'}>
+      <div className={'mb-2 flex items-center justify-between text-sm'}>
+        <div className={'flex-1 text-text-title'}>{t('settings.menu.language')}</div>
+        <div className={'flex items-center'}>
+          <Select
+            sx={{
+              fontSize: '0.85rem',
+            }}
+            variant={'standard'}
+            value={language}
+            onChange={(e) => {
+              const language = e.target.value;
+
+              onChange({
+                language,
+              });
+              i18n.changeLanguage(language);
+            }}
+          >
+            {languages.map((option) => (
+              <MenuItem key={option.key} value={option.key}>
+                {option.title}
+              </MenuItem>
+            ))}
+          </Select>
+        </div>
+      </div>
+    </div>
+  );
 }
 
 export default LanguageSetting;

+ 8 - 5
frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/Menu.tsx

@@ -1,6 +1,7 @@
 import React, { useMemo } from 'react';
 import LanguageIcon from '@mui/icons-material/Language';
 import PaletteOutlined from '@mui/icons-material/PaletteOutlined';
+import { useTranslation } from 'react-i18next';
 
 export enum MenuItem {
   Appearance = 'Appearance',
@@ -8,23 +9,25 @@ export enum MenuItem {
 }
 
 function UserSettingMenu({ selected, onSelect }: { onSelect: (selected: MenuItem) => void; selected: MenuItem }) {
+  const { t } = useTranslation();
+
   const options = useMemo(() => {
     return [
       {
-        label: 'Appearance',
+        label: t('settings.menu.appearance'),
         value: MenuItem.Appearance,
         icon: <PaletteOutlined />,
       },
       {
-        label: 'Language',
+        label: t('settings.menu.language'),
         value: MenuItem.Language,
         icon: <LanguageIcon />,
       },
     ];
-  }, []);
+  }, [t]);
 
   return (
-    <div className={'h-[300px] w-[200px] border-r border-solid border-r-line-border pr-2 text-sm'}>
+    <div className={'h-[300px] w-[200px] border-r border-solid border-r-line-border pr-4 text-sm'}>
       {options.map((option) => {
         return (
           <div
@@ -33,7 +36,7 @@ function UserSettingMenu({ selected, onSelect }: { onSelect: (selected: MenuItem
               onSelect(option.value);
             }}
             className={`my-1 flex h-10 w-full cursor-pointer items-center justify-start rounded-md px-4 py-2 text-text-title ${
-              selected === option.value ? 'bg-fill-hover' : 'hover:bg-fill-hover'
+              selected === option.value ? 'bg-fill-list-hover' : 'hover:text-content-blue-300'
             }`}
           >
             <div className={'mr-2'}>{option.icon}</div>

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/SettingPanel.tsx

@@ -14,7 +14,7 @@ function UserSettingPanel({
   userSettingState?: UserSetting;
   onChange: (setting: Partial<UserSetting>) => void;
 }) {
-  const { theme, themeMode } = userSettingState;
+  const { theme, themeMode, language } = userSettingState;
 
   const options = useMemo(() => {
     return [
@@ -24,14 +24,14 @@ function UserSettingPanel({
       },
       {
         value: MenuItem.Language,
-        icon: <LanguageSetting />,
+        content: <LanguageSetting onChange={onChange} language={language} />,
       },
     ];
-  }, [onChange, theme, themeMode]);
+  }, [language, onChange, theme, themeMode]);
 
   const option = options.find((option) => option.value === selected);
 
-  return <div className={'flex-1 pl-2'}>{option?.content}</div>;
+  return <div className={'flex-1 pl-4'}>{option?.content}</div>;
 }
 
 export default UserSettingPanel;

+ 10 - 3
frontend/appflowy_tauri/src/appflowy_app/components/layout/UserSetting/index.tsx

@@ -10,6 +10,7 @@ import { useAppDispatch, useAppSelector } from '$app/stores/store';
 import { currentUserActions } from '$app_reducers/current-user/slice';
 import { useUserSettingControllerContext } from '$app/components/_shared/app-hooks/useUserSettingControllerContext';
 import { ThemeModePB } from '@/services/backend';
+import { useTranslation } from 'react-i18next';
 
 const SlideTransition = React.forwardRef((props: SlideProps, ref) => {
   return <Slide {...props} direction='up' ref={ref} />;
@@ -19,7 +20,7 @@ function UserSettings({ open, onClose }: { open: boolean; onClose: () => void })
   const userSettingState = useAppSelector((state) => state.currentUser.userSetting);
   const dispatch = useAppDispatch();
   const userSettingController = useUserSettingControllerContext();
-
+  const { t } = useTranslation();
   const [selected, setSelected] = useState<MenuItem>(MenuItem.Appearance);
   const handleChange = useCallback(
     (setting: Partial<UserSetting>) => {
@@ -27,9 +28,15 @@ function UserSettings({ open, onClose }: { open: boolean; onClose: () => void })
 
       dispatch(currentUserActions.setUserSetting(newSetting));
       if (userSettingController) {
+        const language = newSetting.language || 'en';
+
         userSettingController.setAppearanceSetting({
           theme: newSetting.theme || Theme.Default,
-          mode: newSetting.themeMode || ThemeModePB.Light,
+          theme_mode: newSetting.themeMode || ThemeModePB.Light,
+          locale: {
+            language_code: language.split('-')[0],
+            country_code: language.split('-')[1],
+          },
         });
       }
     },
@@ -44,7 +51,7 @@ function UserSettings({ open, onClose }: { open: boolean; onClose: () => void })
       keepMounted
       onClose={onClose}
     >
-      <DialogTitle>{'Settings'}</DialogTitle>
+      <DialogTitle>{t('settings.title')}</DialogTitle>
       <DialogContent className={'flex w-[540px]'}>
         <UserSettingMenu
           onSelect={(selected) => {

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/WorkspaceUser.tsx

@@ -29,7 +29,7 @@ export const WorkspaceUser = () => {
           <PersonOutline />
         </Avatar>
         <span className={'ml-2'}>{currentUser.displayName}</span>
-        <button className={'ml-1 rounded hover:bg-fill-hover'}>
+        <button className={'ml-1 rounded hover:bg-fill-list-hover'}>
           <ArrowDropDown />
         </button>
       </div>

+ 12 - 12
frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx

@@ -5,24 +5,24 @@ export const ColorPalette = () => {
       <h2 className={'mb-4'}>Main</h2>
       <div className={'mb-8 flex flex-wrap items-center'}>
         <div title={'main-accent'} className={'m-2 h-[100px] w-[100px] bg-fill-default'}></div>
-        <div title={'main-hovered'} className={'m-2 h-[100px] w-[100px] bg-fill-hover'}></div>
-        <div title={'main-secondary'} className={'m-2 h-[100px] w-[100px] bg-fill-hover'}></div>
+        <div title={'main-hovered'} className={'m-2 h-[100px] w-[100px] bg-fill-list-hover'}></div>
+        <div title={'main-secondary'} className={'m-2 h-[100px] w-[100px] bg-fill-list-hover'}></div>
         <div title={'main-selector'} className={'m-2 h-[100px] w-[100px] bg-fill-selector'}></div>
         <div title={'main-alert'} className={'m-2 h-[100px] w-[100px] bg-function-info'}></div>
         <div title={'main-warning'} className={'m-2 h-[100px] w-[100px] bg-function-warning'}></div>
         <div title={'main-success'} className={'m-2 h-[100px] w-[100px] bg-function-success'}></div>
       </div>
       <h2 className={'mb-4'}>Tint</h2>
-      <div className={'mb-8 flex flex-wrap items-center text-content-onfill'}>
-        <div title={'tint-1'} className={'m-2 h-[100px] w-[100px] bg-tint-1'}></div>
-        <div title={'tint-2'} className={'m-2 h-[100px] w-[100px] bg-tint-2'}></div>
-        <div title={'tint-3'} className={'m-2 h-[100px] w-[100px] bg-tint-3'}></div>
-        <div title={'tint-4'} className={'m-2 h-[100px] w-[100px] bg-tint-4'}></div>
-        <div title={'tint-5'} className={'m-2 h-[100px] w-[100px] bg-tint-5'}></div>
-        <div title={'tint-6'} className={'m-2 h-[100px] w-[100px] bg-tint-6'}></div>
-        <div title={'tint-7'} className={'m-2 h-[100px] w-[100px] bg-tint-7'}></div>
-        <div title={'tint-8'} className={'m-2 h-[100px] w-[100px] bg-tint-8'}></div>
-        <div title={'tint-9'} className={'m-2 h-[100px] w-[100px] bg-tint-9'}></div>
+      <div className={'mb-8 flex flex-wrap items-center text-text-title'}>
+        <div title={'tint-1'} className={'m-2 h-[100px] w-[100px] bg-tint-pink'}></div>
+        <div title={'tint-2'} className={'m-2 h-[100px] w-[100px] bg-tint-purple'}></div>
+        <div title={'tint-3'} className={'m-2 h-[100px] w-[100px] bg-tint-red'}></div>
+        <div title={'tint-4'} className={'m-2 h-[100px] w-[100px] bg-tint-green'}></div>
+        <div title={'tint-5'} className={'m-2 h-[100px] w-[100px] bg-tint-blue'}></div>
+        <div title={'tint-6'} className={'m-2 h-[100px] w-[100px] bg-tint-yellow'}></div>
+        <div title={'tint-7'} className={'m-2 h-[100px] w-[100px] bg-tint-aqua'}></div>
+        <div title={'tint-8'} className={'m-2 h-[100px] w-[100px] bg-tint-lime'}></div>
+        <div title={'tint-9'} className={'m-2 h-[100px] w-[100px] bg-tint-pink'}></div>
       </div>
       <h2 className={'mb-4'}>Shades</h2>
       <div className={'mb-8 flex flex-wrap items-center'}>

部分文件因为文件数量过多而无法显示