浏览代码

enhance: support whiteboard translations

Konstantinos Kaloutas 2 年之前
父节点
当前提交
fbf365ceff

+ 3 - 1
src/main/frontend/extensions/tldraw.cljs

@@ -5,6 +5,7 @@
             [frontend.components.export :as export]
             [frontend.components.page :as page]
             [frontend.config :as config]
+            [frontend.context.i18n :refer [t]]
             [frontend.db.model :as model]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.route :as route-handler]
@@ -93,7 +94,8 @@
 (def undo (fn [] (history/undo! nil)))
 (def redo (fn [] (history/redo! nil)))
 (defn get-tldraw-handlers [current-whiteboard-name]
-  {:search search-handler
+  {:t (fn [key] (t (keyword key)))
+   :search search-handler
    :queryBlockByUUID (fn [block-uuid]
                        (clj->js
                         (model/query-block-by-uuid (parse-uuid block-uuid))))

+ 84 - 0
src/resources/dicts/en.edn

@@ -249,6 +249,90 @@
  :search/publishing "Search"
  :search "Search or create page"
  :whiteboard/link-whiteboard-or-block "Link whiteboard/page/block"
+ :whiteboard/align-left "Align left"
+ :whiteboard/align-center-horizontally "Align center horizontally"
+ :whiteboard/align-right "Align right"
+ :whiteboard/distribute-horizontally "Distribute horizontally"
+ :whiteboard/align-top "Align top"
+ :whiteboard/align-center-vertically "Align center vertically"
+ :whiteboard/align-bottom "Align bottom"
+ :whiteboard/distribute-vertically "Distribute vertically"
+ :whiteboard/pack-into-rectangle"Pack into rectangle"
+ :whiteboard/zoom-to-fit "Zoom to fit"
+ :whiteboard/ungroup "Ungroup"
+ :whiteboard/group "Group"
+ :whiteboard/cut "Cut"
+ :whiteboard/copy "Copy"
+ :whiteboard/paste "Paste"
+ :whiteboard/paste-as-link "Paste as link"
+ :whiteboard/export "Export"
+ :whiteboard/select-all "Select all"
+ :whiteboard/deselect-all "Deselect all"
+ :whiteboard/lock "Lock"
+ :whiteboard/unlock "Unlock"
+ :whiteboard/delete "Delete"
+ :whiteboard/flip-horizontally "Flip horizontally"
+ :whiteboard/flip-vertically "Flip vertically"
+ :whiteboard/move-to-front "Move to front"
+ :whiteboard/move-to-back "Move to back"
+ :whiteboard/dev-print-shape-props "(Dev) Print shape props"
+ :whiteboard/auto-resize "Auto resize"
+ :whiteboard/expand "Expand"
+ :whiteboard/collapse "Collapse"
+ :whiteboard/website-url "Website url"
+ :whiteboard/reload "Reload"
+ :whiteboard/open-website-url "Open website url"
+ :whiteboard/youtube-url "YouTube url"
+ :whiteboard/open-youtube-url "Open YouTube url"
+ :whiteboard/twitter-url "Twitter url"
+ :whiteboard/open-twitter-url "Open Twitter url"
+ :whiteboard/fill "Fill"
+ :whiteboard/stroke-type "Stroke type"
+ :whiteboard/arrow-head "Arrow head"
+ :whiteboard/bold "Bold"
+ :whiteboard/italic "Italic"
+ :whiteboard/undo "Undo"
+ :whiteboard/redo "Redo"
+ :whiteboard/zoom-in "Zoom in"
+ :whiteboard/zoom-out "Zoom out"
+ :whiteboard/select "Select"
+ :whiteboard/pan "Pan"
+ :whiteboard/add-block-or-page "Add block or page"
+ :whiteboard/draw "Draw"
+ :whiteboard/highlight "Highlight"
+ :whiteboard/eraser "Eraser"
+ :whiteboard/connector "Connector"
+ :whiteboard/text "Text"
+ :whiteboard/color "Color"
+ :whiteboard/select-custom-color "Select custom color"
+ :whiteboard/opacity "Opacity"
+ :whiteboard/extra-small "Extra Small"
+ :whiteboard/small "Small"
+ :whiteboard/medium "Medium"
+ :whiteboard/large "Large"
+ :whiteboard/extra-large "Extra Large"
+ :whiteboard/huge "Huge"
+ :whiteboard/scale-level "Scale level"
+ :whiteboard/rectangle "Rectangle"
+ :whiteboard/circle "Circle"
+ :whiteboard/triangle "Triangle"
+ :whiteboard/shape "Shape"
+ :whiteboard/open-page "Open page" 
+ :whiteboard/open-page-in-sidebar "Open page in sidebar"
+ :whiteboard/remove-link "Remove link"
+ :whiteboard/link "Link"
+ :whiteboard/references "References"
+ :whiteboard/link-to-any-page-or-block "Link to any page or block"
+ :whiteboard/start-typing-to-search "Start typing to search..."
+ :whiteboard/new-block-colon "New block:"
+ :whiteboard/new-block-no-colon "New block"
+ :whiteboard/new-block "New block:"
+ :whiteboard/new-page "New page:"
+ :whiteboard/new-whiteboard "New whiteboard"
+ :whiteboard/search-only-blocks "Search only blocks"
+ :whiteboard/search-only-pages "Search only pages"
+ :whiteboard/cache-outdated "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."
+ :whiteboard/shape-quick-links "Shape Quick Links"
  :page-search "Search in the current page"
  :graph-search "Search graph"
  :home "Home"

+ 8 - 4
tldraw/apps/tldraw-logseq/src/components/ActionBar/ActionBar.tsx

@@ -8,9 +8,13 @@ import { TablerIcon } from '../icons'
 import { Button } from '../Button'
 import { ZoomMenu } from '../ZoomMenu'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 export const ActionBar = observer(function ActionBar(): JSX.Element {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const undo = React.useCallback(() => {
     app.api.undo()
@@ -32,20 +36,20 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
     <div className="tl-action-bar" data-html2canvas-ignore="true">
       {!app.readOnly && (
         <div className="tl-toolbar tl-history-bar">
-          <Button tooltip="Undo" onClick={undo}>
+          <Button tooltip={t('whiteboard/undo')} onClick={undo}>
             <TablerIcon name="arrow-back-up" />
           </Button>
-          <Button tooltip="Redo" onClick={redo}>
+          <Button tooltip={t('whiteboard/redo')} onClick={redo}>
             <TablerIcon name="arrow-forward-up" />
           </Button>
         </div>
       )}
 
       <div className={`tl-toolbar tl-zoom-bar ${app.readOnly ? '' : 'ml-4'}`}>
-        <Button tooltip="Zoom in" onClick={zoomIn} id="tl-zoom-in">
+        <Button tooltip={t('whiteboard/zoom-in')} onClick={zoomIn} id="tl-zoom-in">
           <TablerIcon name="plus" />
         </Button>
-        <Button tooltip="Zoom out" onClick={zoomOut} id="tl-zoom-out">
+        <Button tooltip={t('whiteboard/zoom-out')} onClick={zoomOut} id="tl-zoom-out">
           <TablerIcon name="minus" />
         </Button>
         <Separator.Root className="tl-toolbar-separator" orientation="vertical" />

+ 45 - 14
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -92,13 +92,16 @@ function filterShapeByAction<S extends Shape>(type: ContextBarActionType) {
 
 const AutoResizingAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape | TextShape | HTMLShape>('AutoResizing')
 
   const pressed = shapes.every(s => s.props.isAutoResizing)
 
   return (
     <ToggleInput
-      tooltip="Auto Resize"
+      tooltip={t('whiteboard/auto-resize')}
       toggle={shapes.every(s => s.props.type === 'logseq-portal')}
       className="tl-button"
       pressed={pressed}
@@ -122,6 +125,9 @@ const AutoResizingAction = observer(() => {
 
 const LogseqPortalViewModeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LogseqPortalShape>('LogseqPortalViewMode')
 
   const collapsed = shapes.every(s => s.collapsed)
@@ -131,7 +137,7 @@ const LogseqPortalViewModeAction = observer(() => {
 
   const tooltip = (
     <div className="flex">
-      {collapsed ? 'Expand' : 'Collapse'}
+      {collapsed ? t('whiteboard/expand') : t('whiteboard/collapse')}
       <KeyboardShortcut
         action={collapsed ? 'editor/expand-block-children' : 'editor/collapse-block-children'}
       />
@@ -164,6 +170,9 @@ const ScaleLevelAction = observer(() => {
 
 const IFrameSourceAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<IFrameShape>('IFrameSource')[0]
 
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
@@ -177,16 +186,20 @@ const IFrameSourceAction = observer(() => {
 
   return (
     <span className="flex gap-3">
-      <Button tooltip="Reload" type="button" onClick={handleReload}>
+      <Button tooltip={t('whiteboard/reload')} type="button" onClick={handleReload}>
         <TablerIcon name="refresh" />
       </Button>
       <TextInput
-        title="Website Url"
+        title={t('whiteboard/website-url')}
         className="tl-iframe-src"
         value={`${shape.props.url}`}
         onChange={handleChange}
       />
-      <Button tooltip="Open website url" type="button" onClick={() => window.open(shape.props.url)}>
+      <Button
+        tooltip={t('whiteboard/open-website-url')}
+        type="button"
+        onClick={() => window.open(shape.props.url)}
+      >
         <TablerIcon name="external-link" />
       </Button>
     </span>
@@ -195,6 +208,9 @@ const IFrameSourceAction = observer(() => {
 
 const YoutubeLinkAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<YouTubeShape>('YoutubeLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onYoutubeLinkChange(e.target.value)
@@ -204,13 +220,13 @@ const YoutubeLinkAction = observer(() => {
   return (
     <span className="flex gap-3">
       <TextInput
-        title="YouTube Link"
+        title={t('whiteboard/youtube-url')}
         className="tl-youtube-link"
         value={`${shape.props.url}`}
         onChange={handleChange}
       />
       <Button
-        tooltip="Open YouTube Link"
+        tooltip={t('whiteboard/open-youtube-url')}
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
@@ -222,6 +238,9 @@ const YoutubeLinkAction = observer(() => {
 
 const TwitterLinkAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shape = filterShapeByAction<TweetShape>('TwitterLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onTwitterLinkChange(e.target.value)
@@ -231,13 +250,13 @@ const TwitterLinkAction = observer(() => {
   return (
     <span className="flex gap-3">
       <TextInput
-        title="Twitter Link"
+        title={t('whiteboard/twitter-url')}
         className="tl-twitter-link"
         value={`${shape.props.url}`}
         onChange={handleChange}
       />
       <Button
-        tooltip="Open Twitter Link"
+        tooltip={t('whiteboard/open-twitter-url')}
         type="button"
         onClick={() => window.logseq?.api?.open_external_link?.(shape.props.url)}
       >
@@ -249,6 +268,9 @@ const TwitterLinkAction = observer(() => {
 
 const NoFillAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<BoxShape | PolygonShape | EllipseShape>('NoFill')
   const handleChange = React.useCallback((v: boolean) => {
     app.selectedShapesArray.forEach(s => s.update({ noFill: v }))
@@ -259,7 +281,7 @@ const NoFillAction = observer(() => {
 
   return (
     <ToggleInput
-      tooltip="Fill"
+      tooltip={t('whiteboard/fill')}
       className="tl-button"
       pressed={noFill}
       onPressedChange={handleChange}
@@ -315,6 +337,9 @@ const GeometryAction = observer(() => {
 
 const StrokeTypeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<
     BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape
   >('StrokeType')
@@ -340,7 +365,7 @@ const StrokeTypeAction = observer(() => {
 
   return (
     <ToggleGroupInput
-      title="Stroke Type"
+      title={t('whiteboard/stroke-type')}
       options={StrokeTypeOptions}
       value={value}
       onValueChange={v => {
@@ -357,6 +382,9 @@ const StrokeTypeAction = observer(() => {
 
 const ArrowModeAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<LineShape>('ArrowMode')
 
   const StrokeTypeOptions: ToggleGroupInputOption[] = [
@@ -384,7 +412,7 @@ const ArrowModeAction = observer(() => {
 
   return (
     <ToggleGroupMultipleInput
-      title="Arrow Head"
+      title={t('whiteboard/arrow-head')}
       options={StrokeTypeOptions}
       value={value}
       onValueChange={v => {
@@ -401,6 +429,9 @@ const ArrowModeAction = observer(() => {
 
 const TextStyleAction = observer(() => {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const shapes = filterShapeByAction<TextShape>('TextStyle')
 
   const bold = shapes.every(s => s.props.fontWeight > 500)
@@ -409,7 +440,7 @@ const TextStyleAction = observer(() => {
   return (
     <span className="flex gap-1">
       <ToggleInput
-        tooltip="Bold"
+        tooltip={t('whiteboard/bold')}
         className="tl-button"
         pressed={bold}
         onPressedChange={v => {
@@ -425,7 +456,7 @@ const TextStyleAction = observer(() => {
         <TablerIcon name="bold" />
       </ToggleInput>
       <ToggleInput
-        tooltip="Italic"
+        tooltip={t('whiteboard/italic')}
         className="tl-button"
         pressed={italic}
         onPressedChange={v => {

+ 52 - 46
tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx

@@ -21,7 +21,9 @@ export const ContextMenu = observer(function ContextMenu({
   collisionRef,
 }: ContextMenuProps) {
   const app = useApp()
-  const { handlers } = React.useContext(LogseqContext)
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
   const rContent = React.useRef<HTMLDivElement>(null)
 
   const runAndTransition = (f: Function) => {
@@ -64,26 +66,26 @@ export const ContextMenu = observer(function ContextMenu({
                 <ReactContextMenu.Item>
                   <div className="tl-menu-button-row pb-0">
                     <Button
-                      tooltip="Align left"
+                      tooltip={t('whiteboard/align-left')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Left))}
                     >
                       <TablerIcon name="layout-align-left" />
                     </Button>
                     <Button
-                      tooltip="Align center horizontally"
+                      tooltip={t('whiteboard/align-center-horizontally')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}
                     >
                       <TablerIcon name="layout-align-center" />
                     </Button>
                     <Button
-                      tooltip="Align right"
+                      tooltip={t('whiteboard/align-right')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Right))}
                     >
                       <TablerIcon name="layout-align-right" />
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
-                      tooltip="Distribute horizontally"
+                      tooltip={t('whiteboard/distribute-horizontally')}
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Horizontal))
                       }
@@ -93,26 +95,26 @@ export const ContextMenu = observer(function ContextMenu({
                   </div>
                   <div className="tl-menu-button-row pt-0">
                     <Button
-                      tooltip="Align top"
+                      tooltip={t('whiteboard/align-top')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Top))}
                     >
                       <TablerIcon name="layout-align-top" />
                     </Button>
                     <Button
-                      tooltip="Align center vertically"
+                      tooltip={t('whiteboard/align-center-vertically')}
                       onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}
                     >
                       <TablerIcon name="layout-align-middle" />
                     </Button>
                     <Button
-                      tooltip="Align bottom"
+                      tooltip={t('whiteboard/align-bottom')}
                       onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}
                     >
                       <TablerIcon name="layout-align-bottom" />
                     </Button>
                     <Separator.Root className="tl-toolbar-separator" orientation="vertical" />
                     <Button
-                      tooltip="Distribute vertically"
+                      tooltip={t('whiteboard/distribute-vertically')}
                       onClick={() =>
                         runAndTransition(() => app.distribute(DistributeType.Vertical))
                       }
@@ -127,7 +129,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.packIntoRectangle)}
                 >
                   <TablerIcon className="tl-menu-icon" name="layout-grid" />
-                  Pack into rectangle
+                  {t('whiteboard/pack-into-rectangle')}
                 </ReactContextMenu.Item>
                 <ReactContextMenu.Separator className="menu-separator" />
               </>
@@ -138,7 +140,7 @@ export const ContextMenu = observer(function ContextMenu({
                 className="tl-menu-item"
                 onClick={() => runAndTransition(app.api.zoomToSelection)}
               >
-                Zoom to fit
+                {t('whiteboard/zoom-to-fit')}
                 <KeyboardShortcut action="whiteboard/zoom-to-fit" />
               </ReactContextMenu.Item>
               <ReactContextMenu.Separator className="menu-separator" />
@@ -155,7 +157,7 @@ export const ContextMenu = observer(function ContextMenu({
                     onClick={() => runAndTransition(app.api.unGroup)}
                   >
                     <TablerIcon className="tl-menu-icon" name="ungroup" />
-                    Ungroup
+                    {t('whiteboard/ungroup')}
                     <KeyboardShortcut action="whiteboard/ungroup" />
                   </ReactContextMenu.Item>
                 )}
@@ -166,7 +168,7 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.api.doGroup)}
                     >
                       <TablerIcon className="tl-menu-icon" name="group" />
-                      Group
+                      {t('whiteboard/group')}
                       <KeyboardShortcut action="whiteboard/group" />
                     </ReactContextMenu.Item>
                   )}
@@ -181,7 +183,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.cut)}
                 >
                   <TablerIcon className="tl-menu-icon" name="cut" />
-                  Cut
+                  {t('whiteboard/cut')}
                 </ReactContextMenu.Item>
               )}
               <ReactContextMenu.Item
@@ -189,7 +191,7 @@ export const ContextMenu = observer(function ContextMenu({
                 onClick={() => runAndTransition(app.copy)}
               >
                 <TablerIcon className="tl-menu-icon" name="copy" />
-                Copy
+                {t('whiteboard/copy')}
                 <KeyboardShortcut action="editor/copy" />
               </ReactContextMenu.Item>
             </>
@@ -200,7 +202,7 @@ export const ContextMenu = observer(function ContextMenu({
               onClick={() => runAndTransition(app.paste)}
             >
               <TablerIcon className="tl-menu-icon" name="clipboard" />
-              Paste
+              {t('whiteboard/paste')}
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                   <code>{MOD_KEY}</code> <code>v</code>
@@ -213,7 +215,7 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               onClick={() => runAndTransition(() => app.paste(undefined, true))}
             >
-              Paste as link
+              {t('whiteboard/paste-as-link')}
               <div className="tl-menu-right-slot">
                 <span className="keyboard-shortcut">
                   <code>{MOD_KEY}</code> <code>⇧</code> <code>v</code>
@@ -239,7 +241,7 @@ export const ContextMenu = observer(function ContextMenu({
                 }
               >
                 <TablerIcon className="tl-menu-icon" name="file-export" />
-                Export
+                {t('whiteboard/export')}
                 <div className="tl-menu-right-slot">
                   <span className="keyboard-shortcut"></span>
                 </div>
@@ -251,7 +253,7 @@ export const ContextMenu = observer(function ContextMenu({
             className="tl-menu-item"
             onClick={() => runAndTransition(app.api.selectAll)}
           >
-            Select all
+            {t('whiteboard/select-all')}
             <KeyboardShortcut action="editor/select-parent" />
           </ReactContextMenu.Item>
           {app.selectedShapes?.size > 1 && (
@@ -259,29 +261,33 @@ export const ContextMenu = observer(function ContextMenu({
               className="tl-menu-item"
               onClick={() => runAndTransition(app.api.deselectAll)}
             >
-              Deselect all
-            </ReactContextMenu.Item>
-          )}
-          {!app.readOnly && app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
-            <ReactContextMenu.Item
-              className="tl-menu-item"
-              onClick={() => runAndTransition(() => app.setLocked(true))}
-            >
-              <TablerIcon className="tl-menu-icon" name="lock" />
-              Lock
-              <KeyboardShortcut action="whiteboard/lock" />
-            </ReactContextMenu.Item>
-          )}
-          {!app.readOnly && app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && (
-            <ReactContextMenu.Item
-              className="tl-menu-item"
-              onClick={() => runAndTransition(() => app.setLocked(false))}
-            >
-              <TablerIcon className="tl-menu-icon" name="lock-open" />
-              Unlock
-              <KeyboardShortcut action="whiteboard/unlock" />
+              {t('whiteboard/deselect-all')}
             </ReactContextMenu.Item>
           )}
+          {!app.readOnly &&
+            app.selectedShapes?.size > 0 &&
+            app.selectedShapesArray?.some(s => !s.props.isLocked) && (
+              <ReactContextMenu.Item
+                className="tl-menu-item"
+                onClick={() => runAndTransition(() => app.setLocked(true))}
+              >
+                <TablerIcon className="tl-menu-icon" name="lock" />
+                {t('whiteboard/lock')}
+                <KeyboardShortcut action="whiteboard/lock" />
+              </ReactContextMenu.Item>
+            )}
+          {!app.readOnly &&
+            app.selectedShapes?.size > 0 &&
+            app.selectedShapesArray?.some(s => s.props.isLocked) && (
+              <ReactContextMenu.Item
+                className="tl-menu-item"
+                onClick={() => runAndTransition(() => app.setLocked(false))}
+              >
+                <TablerIcon className="tl-menu-icon" name="lock-open" />
+                {t('whiteboard/unlock')}
+                <KeyboardShortcut action="whiteboard/unlock" />
+              </ReactContextMenu.Item>
+            )}
           {app.selectedShapes?.size > 0 &&
             !app.readOnly &&
             app.selectedShapesArray?.some(s => !s.props.isLocked) && (
@@ -291,7 +297,7 @@ export const ContextMenu = observer(function ContextMenu({
                   onClick={() => runAndTransition(app.api.deleteShapes)}
                 >
                   <TablerIcon className="tl-menu-icon" name="backspace" />
-                  Delete
+                  {t('whiteboard/delete')}
                   <KeyboardShortcut action="editor/delete" />
                 </ReactContextMenu.Item>
                 {app.selectedShapes?.size > 1 && !app.readOnly && (
@@ -302,14 +308,14 @@ export const ContextMenu = observer(function ContextMenu({
                       onClick={() => runAndTransition(app.flipHorizontal)}
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-horizontal" />
-                      Flip horizontally
+                      {t('whiteboard/flip-horizontally')}
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.flipVertical)}
                     >
                       <TablerIcon className="tl-menu-icon" name="flip-vertical" />
-                      Flip vertically
+                      {t('whiteboard/flip-vertically')}
                     </ReactContextMenu.Item>
                   </>
                 )}
@@ -320,14 +326,14 @@ export const ContextMenu = observer(function ContextMenu({
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.bringToFront)}
                     >
-                      Move to front
+                      {t('whiteboard/move-to-front')}
                       <KeyboardShortcut action="whiteboard/bring-to-front" />
                     </ReactContextMenu.Item>
                     <ReactContextMenu.Item
                       className="tl-menu-item"
                       onClick={() => runAndTransition(app.sendToBack)}
                     >
-                      Move to back
+                      {t('whiteboard/move-to-back')}
                       <KeyboardShortcut action="whiteboard/send-to-back" />
                     </ReactContextMenu.Item>
                   </>
@@ -344,7 +350,7 @@ export const ContextMenu = observer(function ContextMenu({
                       }
                     }}
                   >
-                    (Dev) Print shape props
+                    {t('whiteboard/dev-print-shape-props')}
                   </ReactContextMenu.Item>
                 )}
               </>

+ 29 - 11
tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx

@@ -3,6 +3,8 @@ import type { Side } from '@radix-ui/react-popper'
 import { ToolButton } from '../ToolButton'
 import * as Popover from '@radix-ui/react-popover'
 import { TablerIcon } from '../icons'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
   popoverSide?: Side
@@ -12,56 +14,72 @@ interface GeometryToolsProps extends React.HTMLAttributes<HTMLElement> {
 }
 
 export const GeometryTools = observer(function GeometryTools({
-  popoverSide = "left",
+  popoverSide = 'left',
   setGeometry,
   activeGeometry,
   chevron = true,
-  ...rest}: GeometryToolsProps) {
+  ...rest
+}: GeometryToolsProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   const geometries = [
     {
       id: 'box',
       icon: 'square',
-      tooltip: 'Rectangle',
+      tooltip: t('whiteboard/rectangle'),
     },
     {
       id: 'ellipse',
       icon: 'circle',
-      tooltip: 'Circle',
+      tooltip: t('whiteboard/circle'),
     },
     {
       id: 'polygon',
       icon: 'triangle',
-      tooltip: 'Triangle',
+      tooltip: t('whiteboard/triangle'),
     },
   ]
 
   const shapes = {
     id: 'shapes',
     icon: 'triangle-square-circle',
-    tooltip: 'Shape',
+    tooltip: t('whiteboard/shape'),
   }
 
   const activeTool = activeGeometry ? geometries.find(geo => geo.id === activeGeometry) : shapes
 
   return (
     <Popover.Root>
-      <Popover.Trigger asChild >
+      <Popover.Trigger asChild>
         <div {...rest} className="tl-geometry-tools-pane-anchor">
           <ToolButton {...activeTool} tooltipSide={popoverSide} />
-          {chevron &&
+          {chevron && (
             <TablerIcon
               data-selected={activeGeometry}
               className="tl-popover-indicator"
               name="chevron-down-left"
             />
-          }
+          )}
         </div>
       </Popover.Trigger>
 
       <Popover.Content className="tl-popover-content" side={popoverSide} sideOffset={15}>
-        <div className={`tl-toolbar tl-geometry-toolbar ${["left", "right"].includes(popoverSide) ? "flex-col" : "flex-row" }`}>
+        <div
+          className={`tl-toolbar tl-geometry-toolbar ${
+            ['left', 'right'].includes(popoverSide) ? 'flex-col' : 'flex-row'
+          }`}
+        >
           {geometries.map(props => (
-            <ToolButton key={props.id} id={props.id} icon={props.icon} tooltip={activeGeometry ? props.tooltip : ''} handleClick={setGeometry} tooltipSide={popoverSide} />
+            <ToolButton
+              key={props.id}
+              id={props.id}
+              icon={props.icon}
+              tooltip={activeGeometry ? props.tooltip : ''}
+              handleClick={setGeometry}
+              tooltipSide={popoverSide}
+            />
           ))}
         </div>
 

+ 12 - 8
tldraw/apps/tldraw-logseq/src/components/PrimaryTools/PrimaryTools.tsx

@@ -7,9 +7,13 @@ import { GeometryTools } from '../GeometryTools'
 import { ColorInput } from '../inputs/ColorInput'
 import { ScaleInput } from '../inputs/ScaleInput'
 import * as Separator from '@radix-ui/react-separator'
+import { LogseqContext } from '../../lib/logseq-context'
 
 export const PrimaryTools = observer(function PrimaryTools() {
   const app = useApp()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const handleSetColor = React.useCallback((color: string) => {
     app.api.setColor(color)
@@ -37,50 +41,50 @@ export const PrimaryTools = observer(function PrimaryTools() {
       <div className="tl-toolbar tl-tools-floating-panel">
         <ToolButton
           handleClick={() => app.selectTool('select')}
-          tooltip="Select"
+          tooltip={t('whiteboard/select')}
           id="select"
           icon="select-cursor"
         />
         <ToolButton
           handleClick={() => app.selectTool('move')}
-          tooltip="Pan"
+          tooltip={t('whiteboard/pan')}
           id="move"
           icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
         />
         <Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
         <ToolButton
           handleClick={() => app.selectTool('logseq-portal')}
-          tooltip="Add block or page"
+          tooltip={t('whiteboard/add-block-or-page')}
           id="logseq-portal"
           icon="circle-plus"
         />
         <ToolButton
           handleClick={() => app.selectTool('pencil')}
-          tooltip="Draw"
+          tooltip={t('whiteboard/draw')}
           id="pencil"
           icon="ballpen"
         />
         <ToolButton
           handleClick={() => app.selectTool('highlighter')}
-          tooltip="Highlight"
+          tooltip={t('whiteboard/highlight')}
           id="highlighter"
           icon="highlight"
         />
         <ToolButton
           handleClick={() => app.selectTool('erase')}
-          tooltip="Eraser"
+          tooltip={t('whiteboard/eraser')}
           id="erase"
           icon="eraser"
         />
         <ToolButton
           handleClick={() => app.selectTool('line')}
-          tooltip="Connector"
+          tooltip={t('whiteboard/connector')}
           id="line"
           icon="connector"
         />
         <ToolButton
           handleClick={() => app.selectTool('text')}
-          tooltip="Text"
+          tooltip={t('whiteboard/text')}
           id="text"
           icon="text"
         />

+ 2 - 1
tldraw/apps/tldraw-logseq/src/components/QuickLinks/QuickLinks.tsx

@@ -8,6 +8,7 @@ import { BlockLink } from '../BlockLink'
 export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ shape }) => {
   const app = useApp()
   const { handlers } = React.useContext(LogseqContext)
+  const t = handlers.t
   const links = React.useMemo(() => {
     const links = [...(shape.props.refs ?? [])].map<[ref: string, showReferenceContent: boolean]>(
       // user added links should show the referenced block content
@@ -30,7 +31,7 @@ export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ shape }) =>
   if (links.length === 0) return null
 
   return (
-    <div className="tl-quick-links" title="Shape Quick Links">
+    <div className="tl-quick-links" title={t('whiteboard/shape-quick-links')}>
       {links.map(([ref, showReferenceContent]) => {
         return (
           <div key={ref} className="tl-quick-links-row">

+ 8 - 10
tldraw/apps/tldraw-logseq/src/components/QuickSearch/QuickSearch.tsx

@@ -101,6 +101,7 @@ export const LogseqQuickSearch = observer(
     )
     const rInput = React.useRef<HTMLInputElement>(null)
     const { handlers, renderers } = React.useContext(LogseqContext)
+    const t = handlers.t
 
     const finishSearching = React.useCallback((id: string) => {
       onChange(id)
@@ -172,11 +173,11 @@ export const LogseqQuickSearch = observer(
               <LogseqTypeTag active type="BA" />
               {q.length > 0 ? (
                 <>
-                  <strong>New block:</strong>
+                  <strong>{t('whiteboard/new-block')}</strong>
                   {q}
                 </>
               ) : (
-                <strong>New block</strong>
+                <strong>{t('whiteboard/new-block-no-colon')}</strong>
               )}
             </div>
           ),
@@ -195,7 +196,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="PA" />
-                <strong>New page:</strong>
+                <strong>{t('whiteboard/new-page')}</strong>
                 {q}
               </div>
             ),
@@ -210,7 +211,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag active type="WA" />
-                <strong>New whiteboard:</strong>
+                <strong>{t('whiteboard/new-whiteboard')}</strong>
                 {q}
               </div>
             ),
@@ -230,7 +231,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="BS" />
-                Search only blocks
+                {t('whiteboard/search-only-blocks')}
               </div>
             ),
           },
@@ -243,7 +244,7 @@ export const LogseqQuickSearch = observer(
             element: (
               <div className="tl-quick-search-option-row">
                 <LogseqTypeTag type="PS" />
-                Search only pages
+                {t('whiteboard/search-only-pages')}
               </div>
             ),
           }
@@ -302,10 +303,7 @@ export const LogseqQuickSearch = observer(
                     </div>
                   </>
                 ) : (
-                  <div className="tl-quick-search-option-row">
-                    Cache is outdated. Please click the 'Re-index' button in the graph's dropdown
-                    menu.
-                  </div>
+                  <div className="tl-quick-search-option-row">{t('whiteboard/cache-outdated')}</div>
                 ),
               }
             })

+ 9 - 4
tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx

@@ -5,6 +5,7 @@ import { TablerIcon } from '../icons'
 import { PopoverButton } from '../PopoverButton'
 import { Tooltip } from '../Tooltip'
 import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface ColorInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   color?: string
@@ -22,6 +23,10 @@ export function ColorInput({
   setOpacity,
   ...rest
 }: ColorInputProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   function renderColor(color?: string) {
     return color ? (
       <div className="tl-color-bg" style={{ backgroundColor: color }}>
@@ -57,7 +62,7 @@ export function ColorInput({
       arrow
       side={popoverSide}
       label={
-        <Tooltip content={'Color'} side={popoverSide} sideOffset={14}>
+        <Tooltip content={t('whiteboard/color')} side={popoverSide} sideOffset={14}>
           {renderColor(color)}
         </Tooltip>
       }
@@ -82,7 +87,7 @@ export function ColorInput({
                 className="color-input cursor-pointer"
                 id="tl-custom-color-input"
                 type="color"
-                value={isHexColor(color) ? color : "#000000"}
+                value={isHexColor(color) ? color : '#000000'}
                 onChange={handleChangeDebounced}
                 style={{ opacity: isBuiltInColor(color) ? 0 : 1 }}
                 {...rest}
@@ -90,7 +95,7 @@ export function ColorInput({
             </div>
           </div>
           <label htmlFor="tl-custom-color-input" className="cursor-pointer">
-            Select custom color
+            {t('whiteboard/select-custom-color')}
           </label>
         </div>
 
@@ -101,7 +106,7 @@ export function ColorInput({
               onValueCommit={value => setOpacity(value[0])}
               max={1}
               step={0.1}
-              aria-label="Opacity"
+              aria-label={t('whiteboard/opacity')}
               className="tl-slider-root"
             >
               <Slider.Track className="tl-slider-track">

+ 12 - 7
tldraw/apps/tldraw-logseq/src/components/inputs/ScaleInput.tsx

@@ -2,6 +2,8 @@ import { SelectInput, type SelectOption } from '../inputs/SelectInput'
 import type { Side } from '@radix-ui/react-popper'
 import type { SizeLevel } from '../../lib'
 import { useApp } from '@tldraw/react'
+import React from 'react'
+import { LogseqContext } from '../../lib/logseq-context'
 
 interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
   scaleLevel?: SizeLevel
@@ -11,37 +13,40 @@ interface ScaleInputProps extends React.HTMLAttributes<HTMLButtonElement> {
 
 export function ScaleInput({ scaleLevel, compact, popoverSide, ...rest }: ScaleInputProps) {
   const app = useApp<Shape>()
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
 
   const sizeOptions: SelectOption[] = [
     {
-      label: compact ? 'XS' : 'Extra Small',
+      label: compact ? 'XS' : t('whiteboard/extra-small'),
       value: 'xs',
     },
     {
-      label: compact ? 'SM' : 'Small',
+      label: compact ? 'SM' : t('whiteboard/small'),
       value: 'sm',
     },
     {
-      label: compact ? 'MD' : 'Medium',
+      label: compact ? 'MD' : t('whiteboard/medium'),
       value: 'md',
     },
     {
-      label: compact ? 'LG' : 'Large',
+      label: compact ? 'LG' : t('whiteboard/large'),
       value: 'lg',
     },
     {
-      label: compact ? 'XL' : 'Extra Large',
+      label: compact ? 'XL' : t('whiteboard/extra-large'),
       value: 'xl',
     },
     {
-      label: compact ? 'XXL' : 'Huge',
+      label: compact ? 'XXL' : t('whiteboard/huge'),
       value: 'xxl',
     },
   ]
 
   return (
     <SelectInput
-      tooltip="Scale level"
+      tooltip={t('whiteboard/scale-level')}
       options={sizeOptions}
       value={scaleLevel}
       popoverSide={popoverSide}

+ 16 - 7
tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx

@@ -34,6 +34,7 @@ function ShapeLinkItem({
 }) {
   const app = useApp<Shape>()
   const { handlers } = React.useContext(LogseqContext)
+  const t = handlers.t
 
   return (
     <div className="tl-shape-links-panel-item color-level relative">
@@ -42,12 +43,16 @@ function ShapeLinkItem({
       </div>
       <div className="flex-1" />
       {handlers.getBlockPageName(id) !== app.currentPage.name && (
-        <Button tooltip="Open Page" type="button" onClick={() => handlers?.redirectToPage(id)}>
+        <Button
+          tooltip={t('whiteboard/open-page')}
+          type="button"
+          onClick={() => handlers?.redirectToPage(id)}
+        >
           <TablerIcon name="open-as-page" />
         </Button>
       )}
       <Button
-        tooltip="Open Page in Right Sidebar"
+        tooltip={t('whiteboard/open-page-in-sidebar')}
         type="button"
         onClick={() => handlers?.sidebarAddBlock(id, type === 'B' ? 'block' : 'page')}
       >
@@ -56,7 +61,7 @@ function ShapeLinkItem({
       {onRemove && (
         <Button
           className="tl-shape-links-panel-item-remove-button"
-          tooltip="Remove link"
+          tooltip={t('whiteboard/remove-link')}
           type="button"
           onClick={onRemove}
         >
@@ -76,6 +81,10 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
   onRefsChange,
   ...rest
 }: ShapeLinksInputProps) {
+  const {
+    handlers: { t },
+  } = React.useContext(LogseqContext)
+
   const noOfLinks = refs.length + (pageId ? 1 : 0)
   const canAddLink = refs.length === 0
 
@@ -94,7 +103,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
       align="start"
       alignOffset={-6}
       label={
-        <Tooltip content={'Link'} sideOffset={14}>
+        <Tooltip content={t('whiteboard/link')} sideOffset={14}>
           <div className="flex gap-1 relative items-center justify-center px-1">
             <TablerIcon name={noOfLinks > 0 ? 'link' : 'add-link'} />
             {noOfLinks > 0 && <div className="tl-shape-links-count">{noOfLinks}</div>}
@@ -107,7 +116,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
           <div className="tl-shape-links-reference-panel">
             <div className="text-base inline-flex gap-1 items-center">
               <TablerIcon className="opacity-50" name="internal-link" />
-              References
+              {t('whiteboard/references')}
             </div>
             <ShapeLinkItem type={portalType} id={pageId} />
           </div>
@@ -115,7 +124,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
         <div className="tl-shape-links-panel color-level">
           <div className="text-base inline-flex gap-1 items-center">
             <TablerIcon className="opacity-50" name="add-link" />
-            Link to any page or block
+            {t('whiteboard/link-to-any-page-or-block')}
           </div>
 
           {canAddLink && (
@@ -124,7 +133,7 @@ export const ShapeLinksInput = observer(function ShapeLinksInput({
                 width: 'calc(100% - 46px)',
                 marginLeft: '46px',
               }}
-              placeholder="Start typing to search..."
+              placeholder={t('whiteboard/start-typing-to-search')}
               onChange={addNewRef}
             />
           )}

+ 1 - 0
tldraw/apps/tldraw-logseq/src/lib/logseq-context.ts

@@ -42,6 +42,7 @@ export interface LogseqContextValue {
     }>
   }
   handlers: {
+    t: (key: string) => any
     search: (
       query: string,
       filters: { 'pages?': boolean; 'blocks?': boolean; 'files?': boolean }

+ 0 - 1
tldraw/packages/react/src/components/ui/SelectionForeground/handles/RotateHandle.tsx

@@ -1,5 +1,4 @@
 import { observer } from 'mobx-react-lite'
-import * as React from 'react'
 import { useBoundsEvents } from '../../../../hooks'
 
 interface RotateHandleProps {