瀏覽代碼

Enhance: Add more translations, remove unused ones and add linter for unused (#8568)

* chore: remove unused dictionary keys

* wip: add dictionary keys for hardcoded strings

* resolve conflicts

* Add linter to detect :en ui translation keys match used ones

- Fix a couple entries caught by linter
- Copy :command.editor/open-link-in-sidebar entries to
  :help/open-link-in-sidebar as translation keys shouldn't be reused in
  multiple contexts, especially when it's across ui and shortcut dicts

* fix: remove dead keys

* chore: reuse dict keys

* chore: reintroduce useful keys

---------

Co-authored-by: Gabriel Horner <[email protected]>
Konstantinos 2 年之前
父節點
當前提交
2cace8894c

+ 1 - 1
.github/workflows/build.yml

@@ -114,7 +114,7 @@ jobs:
         run: bb lint:ns-docstrings 2>/dev/null
 
       - name: Lint invalid translation entries
-        run: bb lang:invalid-translations
+        run: bb lang:validate-translations
 
   e2e-test:
     runs-on: ubuntu-latest

+ 2 - 2
bb.edn

@@ -99,8 +99,8 @@
   lang:duplicates
   logseq.tasks.lang/list-duplicates
 
-  lang:invalid-translations
-  logseq.tasks.lang/invalid-translations
+  lang:validate-translations
+  logseq.tasks.lang/validate-translations
 
   file-sync:integration-tests
   logseq.tasks.file-sync/integration-tests}

+ 3 - 3
docs/contributing-to-translations.md

@@ -84,9 +84,9 @@ Keys with duplicate values found:
 
 ## Fix Mistakes
 
-Sometimes, we typo the translation key. If that happens, the github CI step of
-`bb lang:invalid-translations` will detect this error and helpfully show you
-what was typoed.
+Sometimes, we typo a translation key or forget to use it. If this happens,
+the github CI step of `bb lang:validate-translations` will detect these errors
+and tell you what's wrong.
 
 ## Add a Language
 

+ 1 - 1
scripts/src/logseq/tasks/dev.clj

@@ -16,7 +16,7 @@
   (doseq [cmd ["clojure -M:clj-kondo --parallel --lint src --cache false"
                "bb lint:carve"
                "bb lint:large-vars"
-               "bb lang:invalid-translations"
+               "bb lang:validate-translations"
                "bb lint:ns-docstrings"]]
     (println cmd)
     (shell cmd)))

+ 66 - 5
scripts/src/logseq/tasks/lang.clj

@@ -1,9 +1,11 @@
 (ns logseq.tasks.lang
   "Tasks related to language translations"
   (:require [clojure.set :as set]
+            [clojure.string :as string]
             [frontend.dicts :as dicts]
             [frontend.modules.shortcut.dicts :as shortcut-dicts]
-            [logseq.tasks.util :as task-util]))
+            [logseq.tasks.util :as task-util]
+            [babashka.process :refer [shell]]))
 
 (defn- get-dicts
   []
@@ -69,8 +71,11 @@
            (sort-by (juxt :file :translation-key))
            task-util/print-table))))
 
-(defn invalid-translations
-  "Lists translation that don't exist in English"
+(defn- validate-non-default-languages
+  "This validation finds any translation keys that don't exist in the default
+  language English. Logseq needs to work out of the box with its default
+  language. This catches mistakes where another language has accidentally typoed
+  keys or added ones without updating :en"
   []
   (let [dicts (get-all-dicts)
         ;; For now defined as :en but clj-kondo analysis could be more thorough
@@ -83,12 +88,68 @@
                         (set/difference (set (keys get-dicts))
                                         valid-keys)))))]
     (if (empty? invalid-dicts)
-      (println "All translations have valid keys!")
+      (println "All non-default translations have valid keys!")
       (do
-        (println "Invalid translation keys found:")
+        (println "\nThese translation keys are invalid because they don't exist in English:")
         (task-util/print-table invalid-dicts)
         (System/exit 1)))))
 
+;; Command to check for manual entries:
+;; grep -E -oh  '\(t [^ ):]+' -r src/main
+(def manual-ui-dicts
+  "Manual list of ui translations because they are dynamic i.e. keyword isn't
+  first arg. Only map values are used in linter as keys are for easily scanning
+  grep result."
+
+  {"(t (shortcut-helper/decorate-namespace" [] ;; shortcuts related so can ignore
+   "(t (keyword" [:color/yellow :color/red :color/pink :color/green :color/blue
+                  :color/purple :color/gray]
+   ;; from 3 files
+   "(t (if" [:asset/show-in-folder :asset/open-in-browser
+             :search-item/whiteboard :search-item/page
+             :page/make-private :page/make-public]
+   "(t (name" [] ;; shortcuts related
+   "(t (dh/decorate-namespace" [] ;; shortcuts related
+   "(t prompt-key" [:select/default-prompt :select.graph/prompt]
+   ;; All args to ui/make-confirm-modal are not keywords
+   "(t title" []
+   "(t subtitle" [:asset/physical-delete]})
+
+(defn- validate-ui-translations-are-used
+  "This validation checks to see that translations done by (t ...) are equal to
+  the ones defined for the default :en lang. This catches translations that have
+  been added in UI but don't have an entry or translations no longer used in the UI"
+  []
+  (let [actual-dicts (->> (shell {:out :string}
+                                 ;; This currently assumes all ui translations
+                                 ;; use (t and src/main. This can easily be
+                                 ;; tweaked as needed
+                                 "grep -E -oh '\\(t :[^ )]+' -r src/main")
+                          :out
+                          string/split-lines
+                          (map #(keyword (subs % 4)))
+                          (concat (mapcat val manual-ui-dicts))
+                          set)
+        expected-dicts (set (keys (:en (get-dicts))))
+        actual-only (set/difference actual-dicts expected-dicts)
+        expected-only (set/difference expected-dicts actual-dicts)]
+    (if (and (empty? actual-only) (empty? expected-only))
+      (println "All defined :en translation keys match the ones that are used!")
+      (do
+        (when (seq actual-only)
+          (println "\nThese translation keys are invalid because they are used in the UI but not defined:")
+          (task-util/print-table (map #(hash-map :invalid-key %) actual-only)))
+        (when (seq expected-only)
+          (println "\nThese translation keys are invalid because they are not used in the UI:")
+          (task-util/print-table (map #(hash-map :invalid-key %) expected-only)))
+        (System/exit 1)))))
+
+(defn validate-translations
+  "Runs multiple translation validations that fail fast if one of them is invalid"
+  []
+  (validate-non-default-languages)
+  (validate-ui-translations-are-used))
+
 (defn list-duplicates
   "Lists translations that are the same as the one in English"
   [& args]

+ 5 - 5
src/main/frontend/components/content.cljs

@@ -33,7 +33,7 @@
    (ui/menu-link
     {:key "cut"
      :on-click #(editor-handler/cut-selection-blocks true)}
-    "Cut"
+    (t :content/cut)
     nil)
    (ui/menu-link
     {:key      "delete"
@@ -44,7 +44,7 @@
    (ui/menu-link
     {:key "copy"
      :on-click editor-handler/copy-selection-blocks}
-    "Copy"
+    (t :content/copy)
     nil)
    (ui/menu-link
     {:key "copy as"
@@ -236,7 +236,7 @@
           {:key      "Cut"
            :on-click (fn [_e]
                        (editor-handler/cut-block! block-id))}
-          "Cut"
+          (t :content/cut)
           nil)
 
          (ui/menu-link
@@ -400,7 +400,7 @@
   [:div {:id id}
    (if hiccup
      hiccup
-     [:div.cursor "Click to edit"])])
+     [:div.cursor (t :content/click-to-edit)])])
 
 (rum/defc non-hiccup-content < rum/reactive
   [id content on-click on-hide config format]
@@ -422,7 +422,7 @@
          {:id id
           :on-click on-click}
          (if (string/blank? content)
-           [:div.cursor "Click to edit"]
+           [:div.cursor (t :content/click-to-edit)]
            content)]))))
 
 (defn- set-draw-iframe-style!

+ 1 - 1
src/main/frontend/components/export.cljs

@@ -14,7 +14,7 @@
   []
   (when-let [current-repo (state/get-current-repo)]
     [:div.export
-     [:h1.title "Export"]
+     [:h1.title (t :export)]
      [:ul.mr-1
       [:li.mb-4
        [:a.font-medium {:on-click #(export/export-repo-as-edn-v2! current-repo)}

+ 1 - 1
src/main/frontend/components/shortcut.cljs

@@ -104,7 +104,7 @@
      [:td.text-left (t :help/block-reference)]
      [:td.text-right [:code block-ref/left-and-right-parens]]]
     [:tr
-     [:td.text-left (t :command.editor/open-link-in-sidebar)]
+     [:td.text-left (t :help/open-link-in-sidebar)]
      [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]
     [:tr
      [:td.text-left (t :help/context-menu)]

+ 3 - 2
src/main/frontend/components/theme.cljs

@@ -11,7 +11,8 @@
             [frontend.components.settings :as settings]
             [frontend.rum :refer [use-mounted]]
             [frontend.storage :as storage]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [frontend.context.i18n :refer [t]]))
 
 (rum/defc container
   [{:keys [route theme on-click current-repo nfs-granted? db-restoring?
@@ -60,7 +61,7 @@
     (rum/use-effect!
      #(let [db-restored? (false? db-restoring?)]
         (if db-restoring?
-          (util/set-title! "Loading")
+          (util/set-title! (t :loading))
           (when (or nfs-granted? db-restored?)
             (route-handler/update-page-title! route))))
      [nfs-granted? db-restoring? route])

+ 1 - 1
src/main/frontend/components/whiteboard.cljs

@@ -214,7 +214,7 @@
           has-checked? (not-empty checked-page-names)]
       [:<>
        [:h1.select-none.flex.items-center.whiteboard-dashboard-title.title
-        [:div "All whiteboards"
+        [:div (t :all-whiteboards)
          [:span.opacity-50
           (str " · " total-whiteboards)]]
         [:div.flex-1]

文件差異過大導致無法顯示
+ 15 - 335
src/main/frontend/dicts.cljc


+ 3 - 2
src/main/frontend/extensions/excalidraw.cljs

@@ -17,7 +17,8 @@
             [goog.object :as gobj]
             [goog.functions :refer [debounce]]
             [rum.core :as rum]
-            [frontend.mobile.util :as mobile-util]))
+            [frontend.mobile.util :as mobile-util]
+            [frontend.context.i18n :refer [t]]))
 
 (def excalidraw (r/adapt-class Excalidraw))
 
@@ -148,7 +149,7 @@
       (cond
         db-restoring?
         [:div.ls-center
-         (ui/loading "Loading")]
+         (ui/loading (t :loading))]
 
         (false? loading?)
         (draw-inner data option)

+ 3 - 2
src/main/frontend/extensions/latex.cljs

@@ -6,7 +6,8 @@
             [frontend.util :as util]
             [frontend.handler.plugin :refer [hook-extensions-enhancer-by-type] :as plugin-handler]
             [promesa.core :as p]
-            [goog.dom :as gdom]))
+            [goog.dom :as gdom]
+            [frontend.context.i18n :refer [t]]))
 
 ;; TODO: extracted to a rum mixin
 (defn loaded? []
@@ -61,7 +62,7 @@
   [id s block? _display?]
   (let [loading? (rum/react *loading?)]
     (if loading?
-      (ui/loading "Loading")
+      (ui/loading (t :loading))
       (let [element (if block?
                       :div.latex
                       :span.latex-inline)]

+ 6 - 5
src/main/frontend/handler/route.cljs

@@ -11,7 +11,8 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [logseq.graph-parser.text :as text]
-            [reitit.frontend.easy :as rfe]))
+            [reitit.frontend.easy :as rfe]
+            [frontend.context.i18n :refer [t]]))
 
 (defn redirect!
   "If `push` is truthy, previous page will be left in history."
@@ -102,13 +103,13 @@
     :repo-add
     "Add another repo"
     :graph
-    "Graph"
+    (t :graph)
     :all-files
-    "All files"
+    (t :all-files)
     :all-pages
-    "All pages"
+    (t :all-pages)
     :all-journals
-    "All journals"
+    (t :all-journals)
     :file
     (str "File " (:path path-params))
     :new-page

+ 10 - 10
src/main/frontend/page.cljs

@@ -7,7 +7,8 @@
             [frontend.handler.search :as search-handler]
             [frontend.handler.notification :as notification]
             [frontend.components.onboarding.quick-tour :as quick-tour]
-            [frontend.handler.plugin :as plugin-handler]))
+            [frontend.handler.plugin :as plugin-handler]
+            [frontend.context.i18n :refer [t]]))
 
 (rum/defc route-view
   [view route-match]
@@ -38,39 +39,38 @@
         [:div.ls-center
          [:div.icon-box.p-1.rounded.mb-3 (ui/icon "bug" {:style {:font-size ui/icon-size}})]
          [:div.text-xl.font-bold
-          "Sorry. Something went wrong!"]
-         [:div.mt-2.mb-2 "Logseq is having a problem. To try to get it back to a
-         working state, please try the following safe steps in order:"]
+          (t :page/something-went-wrong)]
+         [:div.mt-2.mb-2 (t :page/logseq-is-having-a-problem)]
          [:div
           ;; TODO: Enable once multi-window case doesn't result in possible data loss
           #_[:div.flex.flex-row.justify-between.align-items.mb-2
              [:div.flex.flex-col.items-start
-              [:div.text-2xs.uppercase "STEP 1"]
+              [:div.text-2xs.uppercase (t :page/step "1")]
               [:div [:span.font-bold "Reload"] " the app"]]
              [:div (ui/icon "command") (ui/icon "letter-r")]]
           [:div.flex.flex-row.justify-between.align-items.mb-2.items-center.py-4
            [:div.flex.flex-col.items-start
-            [:div.text-2xs.font-bold.uppercase.toned-down "STEP 1"]
+            [:div.text-2xs.font-bold.uppercase.toned-down (t :page/step "1")]
             [:div [:span.highlighted.font-bold "Rebuild"] [:span.toned-down " search index"]]]
              [:div
-              (ui/button "Try"
+              (ui/button (t :page/try)
                          :small? true
                          :on-click (fn []
                                      (search-handler/rebuild-indices! true)))]]
           [:div.flex.flex-row.justify-between.align-items.mb-2.items-center.separator-top.py-4
            [:div.flex.flex-col.items-start
-            [:div.text-2xs.font-bold.uppercase.toned-down "STEP 2"]
+            [:div.text-2xs.font-bold.uppercase.toned-down (t :page/step "2")]
             [:div [:span.highlighted.font-bold "Relaunch"][:span.toned-down " the app"]]
             [:div.text-xs.toned-down "Quit the app and then reopen it."]]
            [:div (ui/icon "command" {:class "rounded-md p-1 mr-2 bg-quaternary"})
             (ui/icon "letter-q" {:class "rounded-md p-1 bg-quaternary"})]]
           [:div.flex.flex-row.justify-between.align-items.mb-4.items-center.separator-top.py-4
            [:div.flex.flex-col.items-start
-            [:div.text-2xs.font-bold.uppercase.toned-down "STEP 3"]
+            [:div.text-2xs.font-bold.uppercase.toned-down (t :page/step "3")]
             [:div [:span.highlighted.font-bold "Clear"] [:span.toned-down " local storage"]]
             [:div.text-xs.toned-down "This does delete minor preferences like dark/light theme preference."]]
            [:div
-            (ui/button "Try"
+            (ui/button (t :page/try)
                        :small? true
                        :on-click (fn []
                                    (.clear js/localStorage)

部分文件因文件數量過多而無法顯示