From 5160741248e567d9feee366ff5533aec9292b50b Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 4 Apr 2026 20:41:06 +0000 Subject: [PATCH] Add Hebrew translation and RTL support via Docker injection Injects custom CSS and JS into the Paperclip UI during Docker build: - RTL layout overrides for Tailwind, Radix UI, and MDXEditor - Hebrew translation script with MutationObserver for dynamic content - Patch script to modify index.html with dir="rtl" and lang="he" Co-Authored-By: Claude Opus 4.6 (1M context) --- Dockerfile | 6 + assets/rtl-override.css | 741 ++++++++++++++++++++++++++++++++ assets/translate-he.js | 918 ++++++++++++++++++++++++++++++++++++++++ patch-html.sh | 2 +- 4 files changed, 1666 insertions(+), 1 deletion(-) create mode 100644 assets/rtl-override.css create mode 100644 assets/translate-he.js diff --git a/Dockerfile b/Dockerfile index 3a29e51..f604241 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf RUN npm install -g paperclipai +# Inject Hebrew RTL + translation into the UI +COPY assets/rtl-override.css /tmp/rtl-override.css +COPY assets/translate-he.js /tmp/translate-he.js +COPY patch-html.sh /tmp/patch-html.sh +RUN bash /tmp/patch-html.sh && rm -f /tmp/patch-html.sh /tmp/rtl-override.css /tmp/translate-he.js + RUN mkdir -p /data/instances/default/data/storage \ /data/instances/default/data/backups \ /data/instances/default/secrets \ diff --git a/assets/rtl-override.css b/assets/rtl-override.css new file mode 100644 index 0000000..7fbe084 --- /dev/null +++ b/assets/rtl-override.css @@ -0,0 +1,741 @@ +/* ========================================================================== + Paperclip AI - RTL Override for Hebrew (dir="rtl") + Generated from analysis of index-CYurTMty.css (Tailwind v4.1.18 + MDXEditor) + + Scoped under html[dir="rtl"] to apply only in RTL mode. + ========================================================================== */ + +/* -------------------------------------------------------------------------- + 1. GLOBAL DIRECTION + -------------------------------------------------------------------------- */ +html[dir="rtl"] { + direction: rtl; +} + +html[dir="rtl"] body { + text-align: right; +} + +/* -------------------------------------------------------------------------- + 2. TAILWIND UTILITY CLASSES - Padding Left/Right Swap + + .pl-* -> padding-right, .pr-* -> padding-left + -------------------------------------------------------------------------- */ +html[dir="rtl"] .pl-1 { padding-left: 0; padding-right: calc(var(--spacing) * 1); } +html[dir="rtl"] .pl-2 { padding-left: 0; padding-right: calc(var(--spacing) * 2); } +html[dir="rtl"] .pl-3 { padding-left: 0; padding-right: calc(var(--spacing) * 3); } +html[dir="rtl"] .pl-4 { padding-left: 0; padding-right: calc(var(--spacing) * 4); } +html[dir="rtl"] .pl-5 { padding-left: 0; padding-right: calc(var(--spacing) * 5); } +html[dir="rtl"] .pl-5\.5 { padding-left: 0; padding-right: calc(var(--spacing) * 5.5); } +html[dir="rtl"] .pl-6 { padding-left: 0; padding-right: calc(var(--spacing) * 6); } +html[dir="rtl"] .pl-7 { padding-left: 0; padding-right: calc(var(--spacing) * 7); } +html[dir="rtl"] .pl-8 { padding-left: 0; padding-right: calc(var(--spacing) * 8); } + +html[dir="rtl"] .pr-1 { padding-right: 0; padding-left: calc(var(--spacing) * 1); } +html[dir="rtl"] .pr-2 { padding-right: 0; padding-left: calc(var(--spacing) * 2); } +html[dir="rtl"] .pr-3 { padding-right: 0; padding-left: calc(var(--spacing) * 3); } +html[dir="rtl"] .pr-6 { padding-right: 0; padding-left: calc(var(--spacing) * 6); } +html[dir="rtl"] .pr-8 { padding-right: 0; padding-left: calc(var(--spacing) * 8); } +html[dir="rtl"] .pr-10 { padding-right: 0; padding-left: calc(var(--spacing) * 10); } + +/* data-inset padding override */ +html[dir="rtl"] .data-\[inset\]\:pl-8[data-inset] { + padding-left: 0; + padding-right: calc(var(--spacing) * 8); +} + +/* Responsive padding overrides */ +html[dir="rtl"] .sm\:pl-1 { padding-left: 0; padding-right: calc(var(--spacing) * 1); } +html[dir="rtl"] .sm\:pr-3 { padding-right: 0; padding-left: calc(var(--spacing) * 3); } + +/* -------------------------------------------------------------------------- + 3. TAILWIND UTILITY CLASSES - Margin Left/Right Swap + + .ml-* -> margin-right, .mr-* -> margin-left + -------------------------------------------------------------------------- */ +html[dir="rtl"] .-ml-1\.5 { margin-left: 0; margin-right: calc(var(--spacing) * -1.5); } +html[dir="rtl"] .ml-0\.5 { margin-left: 0; margin-right: calc(var(--spacing) * 0.5); } +html[dir="rtl"] .ml-1 { margin-left: 0; margin-right: calc(var(--spacing) * 1); } +html[dir="rtl"] .ml-1\.5 { margin-left: 0; margin-right: calc(var(--spacing) * 1.5); } +html[dir="rtl"] .ml-2 { margin-left: 0; margin-right: calc(var(--spacing) * 2); } +html[dir="rtl"] .ml-3 { margin-left: 0; margin-right: calc(var(--spacing) * 3); } +html[dir="rtl"] .ml-4 { margin-left: 0; margin-right: calc(var(--spacing) * 4); } +html[dir="rtl"] .ml-5 { margin-left: 0; margin-right: calc(var(--spacing) * 5); } +html[dir="rtl"] .ml-auto { margin-left: 0; margin-right: auto; } + +html[dir="rtl"] .mr-1 { margin-right: 0; margin-left: calc(var(--spacing) * 1); } +html[dir="rtl"] .mr-1\.5 { margin-right: 0; margin-left: calc(var(--spacing) * 1.5); } +html[dir="rtl"] .mr-2 { margin-right: 0; margin-left: calc(var(--spacing) * 2); } +html[dir="rtl"] .mr-auto { margin-right: 0; margin-left: auto; } + +/* file selector button margin */ +html[dir="rtl"] .file\:mr-4::file-selector-button { + margin-right: 0; + margin-left: calc(var(--spacing) * 4); +} + +/* Responsive margin overrides */ +html[dir="rtl"] .sm\:mr-1 { margin-right: 0; margin-left: calc(var(--spacing) * 1); } +html[dir="rtl"] .md\:ml-auto { margin-left: 0; margin-right: auto; } + +/* -------------------------------------------------------------------------- + 4. TAILWIND UTILITY CLASSES - Left/Right Position Swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .left-0 { left: auto; right: calc(var(--spacing) * 0); } +html[dir="rtl"] .left-1\/2 { left: auto; right: 50%; } +html[dir="rtl"] .left-2 { left: auto; right: calc(var(--spacing) * 2); } +html[dir="rtl"] .left-2\.5 { left: auto; right: calc(var(--spacing) * 2.5); } +html[dir="rtl"] .left-3 { left: auto; right: calc(var(--spacing) * 3); } +html[dir="rtl"] .left-4 { left: auto; right: calc(var(--spacing) * 4); } +html[dir="rtl"] .left-\[-14px\] { left: auto; right: -14px; } +html[dir="rtl"] .left-\[50\%\] { left: auto; right: 50%; } + +html[dir="rtl"] .-right-0\.5 { right: auto; left: calc(var(--spacing) * -0.5); } +html[dir="rtl"] .-right-2 { right: auto; left: calc(var(--spacing) * -2); } +html[dir="rtl"] .right-0 { right: auto; left: calc(var(--spacing) * 0); } +html[dir="rtl"] .right-1\.5 { right: auto; left: calc(var(--spacing) * 1.5); } +html[dir="rtl"] .right-2 { right: auto; left: calc(var(--spacing) * 2); } +html[dir="rtl"] .right-3 { right: auto; left: calc(var(--spacing) * 3); } +html[dir="rtl"] .right-4 { right: auto; left: calc(var(--spacing) * 4); } +html[dir="rtl"] .right-6 { right: auto; left: calc(var(--spacing) * 6); } + +/* Responsive left positioning */ +html[dir="rtl"] .sm\:left-3 { left: auto; right: calc(var(--spacing) * 3); } + +/* -------------------------------------------------------------------------- + 5. TAILWIND UTILITY CLASSES - Text Align Swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .text-left { text-align: right; } +html[dir="rtl"] .text-right { text-align: left; } +html[dir="rtl"] .sm\:text-left { text-align: right; } +html[dir="rtl"] .sm\:text-right { text-align: left; } + +/* -------------------------------------------------------------------------- + 6. TAILWIND UTILITY CLASSES - Float Swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .float-right { float: left; } + +/* -------------------------------------------------------------------------- + 7. TAILWIND UTILITY CLASSES - Border Left/Right Swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .border-l { + border-left-style: none; + border-left-width: 0; + border-right-style: var(--tw-border-style); + border-right-width: 1px; +} + +html[dir="rtl"] .border-l-2 { + border-left-style: none; + border-left-width: 0; + border-right-style: var(--tw-border-style); + border-right-width: 2px; +} + +html[dir="rtl"] .border-l-transparent { + border-left-color: initial; + border-right-color: #0000; +} + +html[dir="rtl"] .border-r { + border-right-style: none; + border-right-width: 0; + border-left-style: var(--tw-border-style); + border-left-width: 1px; +} + +/* Responsive border overrides */ +html[dir="rtl"] .sm\:border-l { + border-left-style: none; + border-left-width: 0; + border-right-style: var(--tw-border-style); + border-right-width: 1px; +} + +html[dir="rtl"] .lg\:border-r { + border-right-style: none; + border-right-width: 0; + border-left-style: var(--tw-border-style); + border-left-width: 1px; +} + +/* -------------------------------------------------------------------------- + 8. TAILWIND UTILITY CLASSES - Rounded Left/Right Swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .rounded-l-md { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: .5rem; + border-bottom-right-radius: .5rem; +} + +html[dir="rtl"] .rounded-r-md { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: .5rem; + border-bottom-left-radius: .5rem; +} + +html[dir="rtl"] .rounded-r-full { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: 3.40282e38px; + border-bottom-left-radius: 3.40282e38px; +} + +/* -------------------------------------------------------------------------- + 9. TAILWIND UTILITY CLASSES - TranslateX Flip + -------------------------------------------------------------------------- */ +html[dir="rtl"] .-translate-x-1\/2 { --tw-translate-x: 50%; } +html[dir="rtl"] .-translate-x-4 { --tw-translate-x: calc(var(--spacing) * 4); } +html[dir="rtl"] .-translate-x-full { --tw-translate-x: 100%; } +html[dir="rtl"] .translate-x-0 { --tw-translate-x: calc(var(--spacing) * 0); } +html[dir="rtl"] .translate-x-0\.5 { --tw-translate-x: calc(var(--spacing) * -0.5); } +html[dir="rtl"] .translate-x-4\.5 { --tw-translate-x: calc(var(--spacing) * -4.5); } +html[dir="rtl"] .translate-x-5 { --tw-translate-x: calc(var(--spacing) * -5); } +html[dir="rtl"] .translate-x-\[-50\%\] { --tw-translate-x: 50%; } + +/* -------------------------------------------------------------------------- + 10. RADIX UI - data-side tooltip/popover offset swap + -------------------------------------------------------------------------- */ +html[dir="rtl"] .data-\[side\=left\]\:-translate-x-1[data-side=left] { + --tw-translate-x: calc(var(--spacing) * 1); +} + +html[dir="rtl"] .data-\[side\=right\]\:translate-x-1[data-side=right] { + --tw-translate-x: calc(var(--spacing) * -1); +} + +/* -------------------------------------------------------------------------- + 11. MDXEditor - Toolbar Components + -------------------------------------------------------------------------- */ + +/* Toolbar root - sticky, scrollable row */ +html[dir="rtl"] [class*="_toolbarRoot_"] { + flex-direction: row-reverse; +} + +/* Toolbar mode switch - push to left in RTL (was margin-left:auto) */ +html[dir="rtl"] [class*="_toolbarModeSwitch_"] { + margin-left: 0; + margin-right: auto; +} + +/* Toolbar separator - swap border sides */ +html[dir="rtl"] [class*="_toolbarRoot_"] div[role="separator"] { + border-left: 1px solid var(--baseBase); + border-right: 1px solid var(--baseBorder); +} + +/* Toolbar button adjacency spacing */ +html[dir="rtl"] [class*="_toolbarButton_"] + [class*="_toolbarButton_"] { + margin-left: 0; + margin-right: var(--spacing-1); +} + +/* Toolbar toggle first/last child border-radius swap */ +html[dir="rtl"] [class*="_toolbarToggleSingleGroup_"]:first-of-type [class*="_toolbarToggleItem_"]:only-child, +html[dir="rtl"] [class*="_toolbarToggleSingleGroup_"]:only-child [class*="_toolbarToggleItem_"]:first-child, +html[dir="rtl"] [class*="_toolbarModeSwitch_"] [class*="_toolbarToggleItem_"]:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: var(--radius-base); + border-bottom-right-radius: var(--radius-base); +} + +html[dir="rtl"] [class*="_toolbarToggleSingleGroup_"]:last-of-type [class*="_toolbarToggleItem_"]:only-child, +html[dir="rtl"] [class*="_toolbarToggleSingleGroup_"]:only-child [class*="_toolbarToggleItem_"]:last-child, +html[dir="rtl"] [class*="_toolbarModeSwitch_"] [class*="_toolbarToggleItem_"]:last-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: var(--radius-base); + border-bottom-left-radius: var(--radius-base); +} + +/* Dropdown arrow - push to right in RTL (was margin-left:auto) */ +html[dir="rtl"] [class*="_toolbarNodeKindSelectDropdownArrow_"], +html[dir="rtl"] [class*="_selectDropdownArrow_"] { + margin-left: 0; + margin-right: auto; +} + +/* Dropdown container - border radius swap */ +html[dir="rtl"] [class*="_toolbarNodeKindSelectContainer_"], +html[dir="rtl"] [class*="_toolbarButtonDropdownContainer_"], +html[dir="rtl"] [class*="_toolbarCodeBlockLanguageSelectContent_"], +html[dir="rtl"] [class*="_selectContainer_"] { + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-base); +} + +html[dir="rtl"] [class*="_toolbarButtonDropdownContainer_"], +html[dir="rtl"] [class*="_toolbarButtonDropdownContainer_"] [class*="_selectItem_"]:first-child { + border-top-right-radius: 0; + border-top-left-radius: var(--radius-base); +} + +/* Select trigger open state - radius swap */ +html[dir="rtl"] [class*="_toolbarNodeKindSelectTrigger_"][data-state="open"], +html[dir="rtl"] [class*="_toolbarButtonSelectTrigger_"][data-state="open"], +html[dir="rtl"] [class*="_selectTrigger_"][data-state="open"] { + border-bottom-right-radius: var(--radius-none); + border-bottom-left-radius: var(--radius-none); +} + +/* Select item last child - radius swap */ +html[dir="rtl"] [class*="_toolbarNodeKindSelectItem_"]:last-child, +html[dir="rtl"] [class*="_selectItem_"]:last-child { + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-base); +} + +/* -------------------------------------------------------------------------- + 12. MDXEditor - CodeMirror & Image Edit Toolbars + Position: was right:0 -> left:0, border-bottom-left -> border-bottom-right + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_codeMirrorToolbar_"] { + right: auto; + left: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-base); +} + +html[dir="rtl"] [class*="_editImageToolbar_"] { + right: auto; + left: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-base); +} + +/* -------------------------------------------------------------------------- + 13. MDXEditor - Property Panel + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_propertyPanelTitle_"] { + padding-left: 0; + padding-right: var(--spacing-2); +} + +html[dir="rtl"] [class*="_propertyEditorTable_"] th { + text-align: right; +} + +html[dir="rtl"] [class*="_propertyEditorTable_"] [class*="_readOnlyColumnCell_"] { + padding-left: initial; + padding-right: 0; +} + +html[dir="rtl"] [class*="_readOnlyColumnCell_"] { + padding-left: initial; + padding-right: 0; +} + +html[dir="rtl"] [class*="_propertyEditorTable_"] td:last-child [class*="_iconButton_"] { + margin-left: 0; + margin-right: var(--spacing-4); +} + +/* -------------------------------------------------------------------------- + 14. MDXEditor - Dialog + -------------------------------------------------------------------------- */ +/* Dialog centered positioning - no swap needed (translate-50% centers) */ + +html[dir="rtl"] [class*="_dialogTitle_"] { + padding-left: 0; + padding-right: var(--spacing-2); +} + +/* Link dialog input button */ +html[dir="rtl"] [class*="_linkDialogInputWrapper_"] > button { + padding-right: 0; + padding-left: var(--spacing-2); +} + +/* Link dialog input dropdown open state */ +html[dir="rtl"] [class*="_linkDialogInputWrapper_"][data-visible-dropdown="true"] { + border-bottom-left-radius: var(--radius-none); + border-bottom-right-radius: var(--radius-none); +} + +/* Link dialog preview anchor */ +html[dir="rtl"] [class*="_linkDialogPreviewAnchor_"] { + margin-right: 0; + margin-left: var(--spacing-1); +} + +/* -------------------------------------------------------------------------- + 15. MDXEditor - Generic Component Name + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_genericComponentName_"] { + padding-right: 0; + padding-left: var(--spacing-2); +} + +/* -------------------------------------------------------------------------- + 16. MDXEditor - Table Editor + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_tableEditor_"] thead > tr > th { + text-align: left; /* was right, swap */ +} + +html[dir="rtl"] [class*="_toolCell_"] { + text-align: left; /* was right, swap */ +} + +html[dir="rtl"] [class*="_leftAlignedCell_"] { + text-align: right; /* was left, swap */ +} + +html[dir="rtl"] [class*="_rightAlignedCell_"] { + text-align: left; /* was right, swap */ +} + +/* Table column editor toolbar separators */ +html[dir="rtl"] [class*="_tableColumnEditorToolbar_"] [role="separator"] { + margin-left: 0; + margin-right: var(--spacing-1); +} + +/* Add column button - was on right side */ +html[dir="rtl"] [class*="_addColumnButton_"] { + margin-left: 0; + margin-right: var(--spacing-px); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: var(--radius-medium); + border-bottom-left-radius: var(--radius-medium); +} + +/* Toggle group first/last child radius swap */ +html[dir="rtl"] [class*="_toggleGroupRoot_"] button:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: var(--radius-base); + border-bottom-right-radius: var(--radius-base); +} + +html[dir="rtl"] [class*="_toggleGroupRoot_"] button:last-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: var(--radius-base); + border-bottom-left-radius: var(--radius-base); +} + +/* -------------------------------------------------------------------------- + 17. MDXEditor - Diff Source Toggle + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_diffSourceToggleWrapper_"] { + margin-left: 0; + margin-right: auto; + right: auto; + left: 0; +} + +/* -------------------------------------------------------------------------- + 18. MDXEditor - Select with Label + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_selectWithLabel_"] { + margin-left: 0; + margin-right: var(--spacing-2); +} + +html[dir="rtl"] [class*="_toolbarTitleMode_"] { + margin-left: 0; + margin-right: var(--spacing-2); +} + +/* -------------------------------------------------------------------------- + 19. MDXEditor - Downshift Autocomplete + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_downshiftInputWrapper_"] > button { + padding-right: 0; + padding-left: var(--spacing-2); +} + +html[dir="rtl"] [class*="_downshiftInputWrapper_"][data-visible-dropdown="true"] { + border-bottom-left-radius: var(--radius-none); + border-bottom-right-radius: var(--radius-none); +} + +html[dir="rtl"] [class*="_downshiftAutocompleteContainer_"] ul { + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-medium); +} + +html[dir="rtl"] [class*="_downshiftAutocompleteContainer_"] ul li:last-of-type { + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--radius-medium); +} + +/* -------------------------------------------------------------------------- + 20. MDXEditor - List Items (Checkbox) + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_listItemChecked_"], +html[dir="rtl"] [class*="_listItemUnchecked_"] { + padding-left: 0; + padding-right: var(--spacing-6); +} + +html[dir="rtl"] [class*="_listItemChecked_"]:before, +html[dir="rtl"] [class*="_listItemUnchecked_"]:before { + left: auto; + right: 0; +} + +html[dir="rtl"] [class*="_listItemChecked_"]:after { + left: auto; + right: var(--spacing-1_5); +} + +/* -------------------------------------------------------------------------- + 21. MDXEditor - Admonitions (Info/Warning/Danger blocks) + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_admonitionDanger_"], +html[dir="rtl"] [class*="_admonitionInfo_"], +html[dir="rtl"] [class*="_admonitionNote_"], +html[dir="rtl"] [class*="_admonitionTip_"], +html[dir="rtl"] [class*="_admonitionCaution_"] { + border-left: none; + border-right: 3px solid var(--admonitionBorder); +} + +/* -------------------------------------------------------------------------- + 22. Paperclip Markdown Rendered Content + -------------------------------------------------------------------------- */ +html[dir="rtl"] .paperclip-mdxeditor-content ul, +html[dir="rtl"] .paperclip-mdxeditor-content ol { + padding-left: 0; + padding-right: 1.6em; +} + +html[dir="rtl"] .paperclip-mdxeditor-content blockquote { + border-left: none; + border-right: 3px solid var(--border); + padding-left: 0; + padding-right: 1em; +} + +html[dir="rtl"] .paperclip-markdown :where(ul, ol) { + padding-left: 0; + padding-right: 1.15rem; +} + +html[dir="rtl"] .paperclip-markdown li { + padding-left: 0; + padding-right: .2rem; +} + +html[dir="rtl"] .paperclip-markdown blockquote { + border-left: none; + border-right: .24rem solid var(--border); + margin-left: initial; + margin-right: 0; + padding-left: 0; + padding-right: .95rem; +} + +@supports (color: color-mix(in lab, red, red)) { + html[dir="rtl"] .paperclip-markdown blockquote { + border-right: .24rem solid color-mix(in oklab, var(--border) 84%, var(--muted-foreground) 16%); + } +} + +html[dir="rtl"] .paperclip-markdown th { + text-align: right; +} + +/* Nested list/ol padding overrides from Tailwind arbitrary selectors */ +html[dir="rtl"] .\[\&_ol\]\:pl-5 ol { + padding-left: 0; + padding-right: calc(var(--spacing) * 5); +} + +html[dir="rtl"] .\[\&_ul\]\:pl-5 ul { + padding-left: 0; + padding-right: calc(var(--spacing) * 5); +} + +/* -------------------------------------------------------------------------- + 23. CodeMirror (inside MDXEditor) - Cursor & Gutters + -------------------------------------------------------------------------- */ +html[dir="rtl"] .paperclip-mdxeditor .cm-cursor, +html[dir="rtl"] .paperclip-mdxeditor .cm-dropCursor { + border-left-color: transparent !important; + border-right-color: #cdd6f4 !important; +} + +html[dir="rtl"] .paperclip-mdxeditor .cm-gutters { + border-right: none !important; + border-left: 1px solid #313244 !important; +} + +/* -------------------------------------------------------------------------- + 24. Sidebar Layout (shadcn/ui sidebar pattern) + + The sidebar CSS variables are defined but the layout depends on the + app's component structure. These overrides handle common patterns. + -------------------------------------------------------------------------- */ + +/* If sidebar is positioned with left:0, move to right:0 */ +html[dir="rtl"] [data-sidebar="sidebar"] { + left: auto; + right: 0; +} + +/* Sidebar rail/resize handle */ +html[dir="rtl"] [data-sidebar="rail"] { + left: auto; + right: 0; +} + +/* Main content margin adjustment when sidebar present */ +html[dir="rtl"] [data-sidebar="content"] { + margin-left: 0; +} + +/* -------------------------------------------------------------------------- + 25. Dialog Overlay - full screen, no directional change needed + But ensure overlay covers correctly + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_dialogOverlay_"] { + left: 0; + right: 0; +} + +/* -------------------------------------------------------------------------- + 26. Radix UI Popover/Dropdown Transform Origins + These use CSS variables, so they auto-adapt. No override needed. + -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- + 27. Group rotation for collapsible triggers (chevron icons) + In RTL, a right-pointing chevron should point left by default + and rotate when open + -------------------------------------------------------------------------- */ +html[dir="rtl"] .group-data-\[state\=open\]\:rotate-90:is(:where(.group)[data-state=open] *) { + rotate: -90deg; +} + +/* -------------------------------------------------------------------------- + 28. General Hebrew typography adjustments + -------------------------------------------------------------------------- */ +html[dir="rtl"] input, +html[dir="rtl"] textarea, +html[dir="rtl"] select { + text-align: right; +} + +html[dir="rtl"] input[type="email"], +html[dir="rtl"] input[type="url"], +html[dir="rtl"] input[type="number"] { + text-align: left; + direction: ltr; +} + +/* Placeholder text alignment */ +html[dir="rtl"] input::placeholder, +html[dir="rtl"] textarea::placeholder { + text-align: right; +} + +/* -------------------------------------------------------------------------- + 29. Scrollbar position (WebKit) - move to left side in RTL + -------------------------------------------------------------------------- */ +html[dir="rtl"] ::-webkit-scrollbar { + /* Scrollbars auto-flip in RTL for most browsers */ +} + +/* -------------------------------------------------------------------------- + 30. Active SVG translate (press feedback) - flip X direction + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_toolbarToggleItem_"]:active svg, +html[dir="rtl"] [class*="_toolbarButton_"]:active svg, +html[dir="rtl"] [class*="_actionButton_"]:active svg, +html[dir="rtl"] [class*="_tableColumnEditorTrigger_"]:active svg, +html[dir="rtl"] [class*="_tableColumnEditorToolbar_"] > button:active svg, +html[dir="rtl"] [class*="_toggleGroupRoot_"] button:active svg, +html[dir="rtl"] [class*="_addColumnButton_"]:active svg, +html[dir="rtl"] [class*="_addRowButton_"]:active svg { + transform: translate(-1px, 1px); +} + +/* -------------------------------------------------------------------------- + 31. Responsive breakpoint overrides + -------------------------------------------------------------------------- */ +@media (min-width: 640px) { + html[dir="rtl"] .sm\:flex-row { + flex-direction: row-reverse; + } +} + +@media (min-width: 768px) { + html[dir="rtl"] .md\:flex-row { + flex-direction: row-reverse; + } +} + +@media (min-width: 1024px) { + html[dir="rtl"] .lg\:flex-row { + flex-direction: row-reverse; + } +} + +@media (min-width: 1280px) { + html[dir="rtl"] .xl\:flex-row { + flex-direction: row-reverse; + } +} + +/* -------------------------------------------------------------------------- + 32. Fix for centered dialogs using left:50% + translate(-50%, -50%) + These should remain centered, so override left->right and flip translate + -------------------------------------------------------------------------- */ +html[dir="rtl"] [class*="_dialogContent_"], +html[dir="rtl"] [class*="_largeDialogContent_"] { + left: auto; + right: 50%; + transform: translate(50%, -50%); +} + +@keyframes rtl-contentShow { + 0% { + opacity: 0; + transform: translate(50%, -48%) scale(.96); + } + to { + opacity: 1; + transform: translate(50%, -50%) scale(1); + } +} + +html[dir="rtl"] [class*="_dialogContent_"], +html[dir="rtl"] [class*="_largeDialogContent_"] { + animation: rtl-contentShow .15s cubic-bezier(.16, 1, .3, 1); +} + +/* -------------------------------------------------------------------------- + 33. Fix Tailwind space-x utilities + These already use margin-inline-start/end, so they should auto-adapt. + No override needed for space-x-* classes (they use logical properties). + -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- + 34. Override for the bottom-positioned elements with safe-area-inset + These are vertical-only, no RTL swap needed. + -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- + 35. Miscellaneous icon and decorative element adjustments + -------------------------------------------------------------------------- */ + +/* Ensure icons in buttons don't get mirrored unintentionally */ +html[dir="rtl"] svg { + /* Most icons should NOT be mirrored. Only directional icons need it. */ +} + +/* Mirror directional icons (arrows, chevrons) */ +html[dir="rtl"] [class*="chevron-left"] svg, +html[dir="rtl"] [class*="chevron-right"] svg, +html[dir="rtl"] [class*="arrow-left"] svg, +html[dir="rtl"] [class*="arrow-right"] svg { + transform: scaleX(-1); +} diff --git a/assets/translate-he.js b/assets/translate-he.js new file mode 100644 index 0000000..31ba3ba --- /dev/null +++ b/assets/translate-he.js @@ -0,0 +1,918 @@ +/** + * Paperclip AI - Hebrew Translation Injection + * Replaces English UI text with Hebrew after React renders. + * Uses MutationObserver to catch route changes and dynamic content. + */ +(function () { + "use strict"; + + // ========================================================================= + // TRANSLATION MAP + // ========================================================================= + const T = { + // --- Sidebar / Navigation --- + "Dashboard": "לוח בקרה", + "Inbox": "תיבת דואר", + "Work": "עבודה", + "Issues": "משימות", + "Routines": "שגרות", + "Goals": "יעדים", + "Company": "חברה", + "Org": "ארגון", + "Skills": "כישורים", + "Costs": "עלויות", + "Activity": "פעילות", + "Settings": "הגדרות", + "General": "כללי", + "Heartbeats": "פעימות", + "Experimental": "ניסיוני", + "Plugins": "תוספים", + "Me": "אני", + "Agents": "סוכנים", + "Projects": "פרויקטים", + "Home": "בית", + "Create": "צור", + "Not Found": "לא נמצא", + "Open sidebar": "פתח סרגל צד", + "Close sidebar": "סגור סרגל צד", + "Skip to Main Content": "דלג לתוכן הראשי", + + // --- Common Buttons --- + "Save": "שמור", + "Cancel": "ביטול", + "Delete": "מחק", + "Edit": "ערוך", + "Update": "עדכן", + "Close": "סגור", + "Confirm": "אשר", + "Back": "חזור", + "Next": "הבא", + "Previous": "הקודם", + "Done": "סיום", + "Apply": "החל", + "Continue": "המשך", + "Dismiss": "סגור", + "Clear": "נקה", + "Reset": "אפס", + "Retry": "נסה שוב", + "Submit": "שלח", + "OK": "אישור", + "Yes": "כן", + "No": "לא", + "Go": "עבור", + "Search": "חיפוש", + "Filter": "סינון", + "Sort": "מיון", + "Copy": "העתק", + "Download": "הורד", + "Upload": "העלה", + "Export": "ייצוא", + "Import": "ייבוא", + "Enable": "הפעל", + "Disable": "השבת", + "Install": "התקן", + "Uninstall": "הסר התקנה", + "Start": "התחל", + "Stop": "עצור", + "Pause": "השהה", + "Resume": "חדש", + "Run": "הפעל", + "Launch": "הפעל", + "Approve": "אשר", + "Reject": "דחה", + "Archive": "ארכיון", + "Restore": "שחזר", + "Pin": "הצמד", + "Revoke": "בטל", + "Assign": "הקצה", + "Block": "חסום", + "Link": "קישור", + "View": "צפה", + "Show": "הצג", + "Hide": "הסתר", + "Open": "פתח", + "Select": "בחר", + "Remove": "הסר", + "Comment": "תגובה", + "Restart": "הפעל מחדש", + "Schedule": "תזמן", + "Configure": "הגדר", + "Choose": "בחר", + + // --- Action Buttons --- + "Save changes": "שמור שינויים", + "Save Configuration": "שמור הגדרות", + "Save note": "שמור הערה", + "Save routine": "שמור שגרה", + "Save trigger": "שמור טריגר", + "Save failed": "השמירה נכשלה", + "Add Agent": "הוסף סוכן", + "Add Goal": "הוסף יעד", + "Add Project": "הוסף פרויקט", + "Add company": "הוסף חברה", + "Add item": "הוסף פריט", + "Add trigger": "הוסף טריגר", + "Add a new agent": "הוסף סוכן חדש", + "Add a skill source": "הוסף מקור כישורים", + "Add a short note": "הוסף הערה קצרה", + "Add a comment...": "הוסף תגובה...", + "Add a description...": "הוסף תיאור...", + "Add description...": "הוסף תיאור...", + "Add instructions...": "הוסף הוראות...", + "Adding...": "מוסיף...", + "Create Issue": "צור משימה", + "Create Item": "צור פריט", + "Create Account": "צור חשבון", + "Create API Key": "צור מפתח API", + "Create agent": "צור סוכן", + "Create document": "צור מסמך", + "Create goal": "צור יעד", + "Create label": "צור תווית", + "Create one": "צור אחד", + "Create one here": "צור כאן", + "Create project": "צור פרויקט", + "Create routine": "צור שגרה", + "Create skill": "צור כישור", + "Create new agent": "צור סוכן חדש", + "Create new company": "צור חברה חדשה", + "Create new issue": "צור משימה חדשה", + "Create new project": "צור פרויקט חדש", + "Create another company": "צור חברה נוספת", + "Create a new agent": "צור סוכן חדש", + "Create your Paperclip account": "צור את חשבון Paperclip שלך", + "Create your first agent": "צור את הסוכן הראשון שלך", + "Create your first company": "צור את החברה הראשונה שלך", + "Delete Company": "מחק חברה", + "Delete attachment": "מחק קובץ מצורף", + "Delete document": "מחק מסמך", + "Delete failed": "המחיקה נכשלה", + "Delete image": "מחק תמונה", + "Delete issue": "מחק משימה", + "Edit document": "ערוך מסמך", + "Edit image": "ערוך תמונה", + "Copy Agent ID": "העתק מזהה סוכן", + "Copy to clipboard": "העתק ללוח", + "Copy snippet": "העתק קטע", + "Copy document": "העתק מסמך", + "Approve CLI access": "אשר גישת CLI", + "Approve Paperclip CLI access": "אשר גישת Paperclip CLI", + "Approving...": "מאשר...", + "Archive company": "העבר חברה לארכיון", + "Assign Task": "הקצה משימה", + "Assign to me": "הקצה לי", + "Assign to requester": "הקצה למבקש", + "Import company": "ייבא חברה", + "Import complete": "הייבוא הושלם", + "Import failed": "הייבוא נכשל", + "Import preview": "תצוגה מקדימה של ייבוא", + "Import source": "מקור ייבוא", + "Export company": "ייצא חברה", + "Export downloaded": "הייצוא הורד", + "Export failed": "הייצוא נכשל", + "Install Plugin": "התקן תוסף", + "Install Example": "התקן דוגמה", + "Install update": "התקן עדכון", + "Uninstall Plugin": "הסר תוסף", + "Run now": "הפעל עכשיו", + "Run routine": "הפעל שגרה", + "Run Activity": "הפעל פעילות", + "Run Heartbeat": "הפעל פעימה", + "Open board": "פתח לוח", + "Open budgets": "פתח תקציבים", + "Open command palette": "פתח לוח פקודות", + "Open Command Palette": "פתח לוח פקודות", + "Open dashboard": "פתח לוח בקרה", + "Open docs": "פתח מסמכים", + "Open Settings": "פתח הגדרות", + "Open Issues": "משימות פתוחות", + "Open Side Panel": "פתח פאנל צדי", + "View agent": "צפה בסוכן", + "View details": "צפה בפרטים", + "View full error": "צפה בשגיאה מלאה", + "View inbox": "צפה בתיבת דואר", + "View run": "צפה בהרצה", + "Mark all as read": "סמן הכל כנקרא", + "Mark as done": "סמן כבוצע", + "Mark as read": "סמן כנקרא", + "Clear all": "נקה הכל", + "Clear repo": "נקה מאגר", + "Clear local folder": "נקה תיקיה מקומית", + "Sign in": "התחבר", + "Sign in required": "נדרשת התחברות", + "Sign in to Paperclip": "התחבר ל-Paperclip", + "Sign in / Create account": "התחבר / צור חשבון", + "Go home": "חזור לדף הבית", + "Back to all workspaces": "חזור לכל סביבות העבודה", + "Back to approvals": "חזור לאישורים", + "Back to runs": "חזור להרצות", + "Back to workspaces": "חזור לסביבות עבודה", + "Check for updates": "בדוק עדכונים", + "Start Onboarding": "התחל הקמה", + "Stop editing": "הפסק עריכה", + "Deploy to production": "פרוס לייצור", + "Restart Required": "נדרש הפעלה מחדש", + "Reset Sessions": "אפס הפעלות", + "Reset defaults": "אפס ברירות מחדל", + "Reset filters": "אפס מסננים", + "Download document": "הורד מסמך", + "Upload attachment": "העלה קובץ מצורף", + "Upload an image": "העלה תמונה", + "Upload failed": "ההעלאה נכשלה", + "Filter Bar": "סרגל סינון", + "Filter by agent name": "סנן לפי שם סוכן", + "Filter by type": "סנן לפי סוג", + "Filter skills": "סנן כישורים", + "Show properties": "הצג מאפיינים", + "Show secret": "הצג סוד", + "Hide secret": "הסתר סוד", + "Show full log": "הצג יומן מלא", + "Hide full log": "הסתר יומן מלא", + "Set repo": "הגדר מאגר", + "Change repo": "שנה מאגר", + "Submit join request": "שלח בקשת הצטרפות", + "Join request submitted": "בקשת ההצטרפות נשלחה", + "Accept bootstrap invite": "קבל הזמנת הקמה", + "Return to latest": "חזור לעדכני", + "Jump to live": "עבור לשידור חי", + "Select company": "בחר חברה", + "Select model": "בחר מודל", + "Select status": "בחר סטטוס", + "Select an option": "בחר אפשרות", + "Watch issue": "עקוב אחר משימה", + "Wake now": "העיר עכשיו", + "Keep my draft": "שמור את הטיוטה שלי", + "Keep paused": "השאר מושהה", + "Discard Draft": "מחק טיוטה", + + // --- Navigation Tabs --- + "Overview": "סקירה כללית", + "Instructions": "הוראות", + "Configuration": "הגדרות", + "Runs": "הרצות", + "Budget": "תקציב", + "Workspaces": "סביבות עבודה", + "All": "הכל", + "Active": "פעיל", + "Paused": "מושהה", + "Error": "שגיאה", + "Recent": "אחרונים", + "Mine": "שלי", + "Unread": "לא נקרא", + "Status": "סטטוס", + + // --- Status Labels --- + "Backlog": "רשימת המתנה", + "Todo": "לביצוע", + "In Progress": "בביצוע", + "In Review": "בסקירה", + "Planned": "מתוכנן", + "Completed": "הושלם", + "Cancelled": "בוטל", + "Critical": "קריטי", + "High": "גבוה", + "Medium": "בינוני", + "Low": "נמוך", + "Default": "ברירת מחדל", + "Blocked": "חסום", + "Queued": "בתור", + "Running": "פועל", + "Enabled": "מופעל", + "Disabled": "מושבת", + "Pending": "ממתין", + "Resolved": "נפתר", + "Unknown": "לא ידוע", + "New": "חדש", + "Archived": "בארכיון", + "Passed": "עבר", + "Failed": "נכשל", + "Managed": "מנוהל", + "Live": "חי", + "Observed": "נצפה", + "Cached": "במטמון", + "Sealed": "חתום", + "Recommended": "מומלץ", + "Beta": "בטא", + "Unlimited budget": "תקציב ללא הגבלה", + "Under budget": "בתוך התקציב", + "Out of date": "לא מעודכן", + "Up to date": "מעודכן", + "Not set.": "לא הוגדר.", + "Not installed": "לא מותקן", + + // --- Thinking Effort --- + "Auto": "אוטומטי", + "Minimal": "מינימלי", + "X-High": "גבוה מאוד", + "Max": "מקסימום", + + // --- Workspace Options --- + "Project default": "ברירת מחדל של פרויקט", + "New isolated workspace": "סביבת עבודה מבודדת חדשה", + "Reuse existing workspace": "שימוש חוזר בסביבה קיימת", + "Remote git repo": "מאגר git מרוחק", + "Local git checkout": "checkout מקומי של git", + "Local non-git path": "נתיב מקומי ללא git", + "Remote-managed workspace": "סביבת עבודה מנוהלת מרחוק", + "Git worktree": "Git worktree", + "Local folder": "תיקיה מקומית", + + // --- Frequency Options --- + "Every minute": "כל דקה", + "Every hour": "כל שעה", + "Every day": "כל יום", + "Weekdays": "ימי חול", + "Weekly": "שבועי", + "Monthly": "חודשי", + "Custom (cron)": "מותאם אישית (cron)", + + // --- Dashboard Cards --- + "Agents Enabled": "סוכנים מופעלים", + "Tasks In Progress": "משימות בביצוע", + "Month Spend": "הוצאות החודש", + "Pending Approvals": "אישורים ממתינים", + "Active Agents": "סוכנים פעילים", + "Active incidents": "תקלות פעילות", + "Active Keys": "מפתחות פעילים", + "Companies": "חברות", + "Live Runs": "הרצות חיות", + "Recent Activity": "פעילות אחרונה", + "Recent Issues": "משימות אחרונות", + "Recent Tasks": "משימות אחרונות", + "Success Rate": "אחוז הצלחה", + "Failed runs": "הרצות שנכשלו", + "Cost Summary": "סיכום עלויות", + + // --- Page Titles --- + "Company Settings": "הגדרות חברה", + "Instance Settings": "הגדרות מופע", + "Approvals": "אישורים", + "Plugin Manager": "מנהל תוספים", + "Available Plugins": "תוספים זמינים", + "Installed Plugins": "תוספים מותקנים", + "Design Guide": "מדריך עיצוב", + "Org Chart": "מבנה ארגוני", + "Finance": "כספים", + "Budgets": "תקציבים", + "Budget control plane": "מישור בקרת תקציב", + "Scheduler Heartbeats": "פעימות מתזמן", + "Run Detail": "פרטי הרצה", + "Issue List": "רשימת משימות", + "Issue Properties": "מאפייני משימה", + "Execution Workspaces": "סביבות ביצוע", + "Company Packages": "חבילות חברה", + "Keyboard Shortcuts": "קיצורי מקלדת", + "Danger Zone": "אזור מסוכן", + "New Agent": "סוכן חדש", + "New Company": "חברה חדשה", + "New Issue": "משימה חדשה", + "New document": "מסמך חדש", + "New issue": "משימה חדשה", + "New project": "פרויקט חדש", + "New routine": "שגרה חדשה", + "Revision history": "היסטוריית גרסאות", + + // --- Form Labels --- + "Name": "שם", + "Title": "כותרת", + "Description": "תיאור", + "Priority": "עדיפות", + "Assignee": "מוקצה", + "Project": "פרויקט", + "Labels": "תוויות", + "Created": "נוצר", + "Updated": "עודכן", + "Target Date": "תאריך יעד", + "Start date": "תאריך התחלה", + "Lead": "אחראי", + "Author": "מחבר", + "Email": "אימייל", + "Password": "סיסמה", + "Company name": "שם חברה", + "Agent name": "שם סוכן", + "Branch name": "שם ענף", + "Branch template": "תבנית ענף", + "Base ref": "הפניית בסיס", + "Repo URL": "כתובת מאגר", + "Brand color": "צבע מותג", + "Command": "פקודה", + "Adapter type": "סוג מתאם", + "Prompt Template": "תבנית פרומפט", + "Environment variables": "משתני סביבה", + "Timeout (sec)": "זמן קצוב (שניות)", + "Max concurrent runs": "הרצות מקבילות מקסימום", + "Max turns per run": "סיבובים מקסימום להרצה", + "Model": "מודל", + "Thinking effort": "מאמץ חשיבה", + "Reports to": "מדווח ל", + "Capabilities": "יכולות", + "Capabilities (optional)": "יכולות (אופציונלי)", + "Role": "תפקיד", + "Budget (USD)": "תקציב (דולר)", + "New budget (USD)": "תקציב חדש (דולר)", + "Heartbeat on interval": "פעימה במרווחי זמן", + "Wake on demand": "התעוררות לפי דרישה", + "Default model": "מודל ברירת מחדל", + "Collision strategy": "אסטרטגיית התנגשות", + "Description (optional)": "תיאור (אופציונלי)", + "Workspace name": "שם סביבת עבודה", + "Execution workspace": "סביבת ביצוע", + "Provision command": "פקודת הקמה", + "Teardown command": "פקודת פירוק", + "Cleanup command": "פקודת ניקוי", + "Runtime services": "שירותי ריצה", + "Plugin Key": "מפתח תוסף", + "Plugin ID": "מזהה תוסף", + "Key": "מפתח", + "Skill name": "שם כישור", + "Document key": "מפתח מסמך", + "Variables": "משתנים", + "Default value": "ערך ברירת מחדל", + "Source": "מקור", + "Mode": "מצב", + "Kind": "סוג", + "Type": "סוג", + "Concurrency": "מקביליות", + "Version": "גרסה", + "PID": "PID", + "Uptime": "זמן פעילות", + "Auto-Restart On": "הפעלה מחדש אוטומטית", + "AI feedback sharing": "שיתוף משוב AI", + "Feedback Sharing": "שיתוף משוב", + + // --- Placeholders --- + "Search icons...": "חפש אייקונים...", + "Search issues, agents, projects...": "חפש משימות, סוכנים, פרויקטים...", + "Search issues...": "חפש משימות...", + "Search assignees...": "חפש מוקצים...", + "Search labels...": "חפש תוויות...", + "Search projects...": "חפש פרויקטים...", + "Search models...": "חפש מודלים...", + "Search files...": "חפש קבצים...", + "Search inbox\u2026": "חפש בתיבת דואר\u2026", + "Type a command or search...": "הקלד פקודה או חפש...", + "Issue title": "כותרת משימה", + "Project name": "שם פרויקט", + "Target date": "תאריך יעד", + "Goal title": "כותרת יעד", + "Leave a comment...": "השאר תגובה...", + "Write something...": "כתוב משהו...", + "Enter a name": "הזן שם", + "Describe...": "תאר...", + "Choose a value": "בחר ערך", + "Choose frequency...": "בחר תדירות...", + "Category": "קטגוריה", + "Approval status": "סטטוס אישור", + "Routine title": "כותרת שגרה", + "Optional title": "כותרת אופציונלית", + "Markdown body": "גוף Markdown", + "New label": "תווית חדשה", + "Short description": "תיאור קצר", + "Optional company description": "תיאור חברה אופציונלי", + "Execution workspace name": "שם סביבת ביצוע", + "NPM Package Name": "שם חבילת NPM", + + // --- Empty States --- + "No results found.": "לא נמצאו תוצאות.", + "No issues": "אין משימות", + "No labels": "אין תוויות", + "No goal": "אין יעד", + "No project": "אין פרויקט", + "No parent": "אין הורה", + "No manager": "אין מנהל", + "No assignee": "אין מוקצה", + "No value": "אין ערך", + "No default": "אין ברירת מחדל", + "No runs yet": "אין הרצות עדיין", + "No runs yet.": "אין הרצות עדיין.", + "No revisions yet": "אין גרסאות עדיין", + "No icons match": "לא נמצאו אייקונים", + "No agents match the selected filter.": "אין סוכנים שתואמים למסנן שנבחר.", + "No agents attached": "לא צורפו סוכנים", + "No active API keys.": "אין מפתחות API פעילים.", + "No activity yet.": "אין פעילות עדיין.", + "No plugins installed": "לא הותקנו תוספים", + "No goals.": "אין יעדים.", + "No tasks yet.": "אין משימות עדיין.", + "No triggers configured yet.": "לא הוגדרו טריגרים עדיין.", + "No description": "אין תיאור", + "No diagnostics": "אין אבחנות", + "No company selected": "לא נבחרה חברה", + "No cost data yet.": "אין נתוני עלויות עדיין.", + "No cost events yet.": "אין אירועי עלות עדיין.", + "No items added yet.": "לא נוספו פריטים עדיין.", + "No recent agent runs.": "אין הרצות סוכנים אחרונות.", + "No recent issues.": "אין משימות אחרונות.", + "No sub-goals.": "אין תת-יעדים.", + "No sub-issues.": "אין תת-משימות.", + "None": "ללא", + "Unassigned": "לא מוקצה", + "You have no agents.": "אין לך סוכנים.", + "No unsaved changes.": "אין שינויים שלא נשמרו.", + "No configuration options available.": "אין אפשרויות הגדרה זמינות.", + + // --- Loading States --- + "Loading...": "טוען...", + "Loading\u2026": "טוען\u2026", + "Loading companies...": "טוען חברות...", + "Loading plugins...": "טוען תוספים...", + "Loading revisions...": "טוען גרסאות...", + "Loading workspaces...": "טוען סביבות עבודה...", + "Loading workspace\u2026": "טוען סביבת עבודה\u2026", + "Loading run logs...": "טוען יומני הרצה...", + "Loading log...": "טוען יומן...", + "Loading keys...": "טוען מפתחות...", + "Loading invite...": "טוען הזמנה...", + "Loading plugin details...": "טוען פרטי תוסף...", + "Checking health...": "בודק תקינות...", + "Uploading logo...": "מעלה לוגו...", + "Building export...": "בונה ייצוא...", + + // --- Toast & Notifications --- + "Copied!": "הועתק!", + "Saved": "נשמר", + "Approval confirmed": "האישור אושר", + "CLI access approved": "גישת CLI אושרה", + "Bootstrap complete": "ההקמה הושלמה", + "Action failed": "הפעולה נכשלה", + "Comment failed": "התגובה נכשלה", + "Copy failed": "ההעתקה נכשלה", + "Archive failed": "הארכוב נכשל", + "Approve failed": "האישור נכשל", + "Reject failed": "הדחייה נכשלה", + "Update failed": "העדכון נכשל", + "Routine created": "השגרה נוצרה", + "Routine saved": "השגרה נשמרה", + "Routine run failed": "הרצת השגרה נכשלה", + "Routine run started": "הרצת השגרה החלה", + "Skill created": "הכישור נוצר", + "Skill saved": "הכישור נשמר", + "Skill updated": "הכישור עודכן", + "Plugin installed successfully": "התוסף הותקן בהצלחה", + "Plugin uninstalled successfully": "התוסף הוסר בהצלחה", + "Plugin enabled": "התוסף הופעל", + "Plugin disabled": "התוסף הושבת", + "Plugin error": "שגיאת תוסף", + "Trigger added": "הטריגר נוסף", + "Trigger deleted": "הטריגר נמחק", + "Trigger saved": "הטריגר נשמר", + "Run completed": "ההרצה הושלמה", + "Run failed": "ההרצה נכשלה", + "Command failed": "הפקודה נכשלה", + "Invite not available": "ההזמנה לא זמינה", + "Invite not found": "ההזמנה לא נמצאה", + "Interrupt requested": "התבקשה הפסקה", + "Interrupt failed": "ההפסקה נכשלה", + + // --- Error Messages --- + "An error was thrown.": "אירעה שגיאה.", + "Authentication failed": "ההתחברות נכשלה", + "Unknown error": "שגיאה לא ידועה", + "Invalid JSON.": "JSON לא תקין.", + "Failed to create issue. Try again.": "יצירת המשימה נכשלה. נסה שוב.", + "Failed to create company": "יצירת החברה נכשלה", + "Failed to create agent": "יצירת הסוכן נכשלה", + "Failed to save": "השמירה נכשלה", + "Failed to approve": "האישור נכשל", + "Failed to reject": "הדחייה נכשלה", + "Instance setup required": "נדרשת הגדרת מופע", + + // --- Dialog & Confirmation --- + "Delete this company and all its data? This cannot be undone.": "למחוק חברה זו וכל הנתונים שלה? לא ניתן לבטל פעולה זו.", + "Delete this document? This cannot be undone.": "למחוק מסמך זה? לא ניתן לבטל פעולה זו.", + "Mark all as read?": "לסמן הכל כנקרא?", + "Already have an account?": "כבר יש לך חשבון?", + + // --- Section Headings --- + "Actions": "פעולות", + "Advanced": "מתקדם", + "Alerts": "התראות", + "Appearance": "מראה", + "Attachments": "קבצים מצורפים", + "Automation": "אוטומציה", + "Categories": "קטגוריות", + "Cleanup": "ניקוי", + "Codebase": "בסיס קוד", + "Comments": "תגובות", + "Company skills": "כישורי חברה", + "Config": "הגדרות", + "Context": "הקשר", + "Controls": "בקרה", + "Credits": "זיכויים", + "Debits": "חיובים", + "Details": "פרטים", + "Documents": "מסמכים", + "Environment": "סביבה", + "Error Details": "פרטי שגיאה", + "Files": "קבצים", + "Filters": "מסננים", + "Git status": "סטטוס Git", + "Hiring": "גיוס", + "Identity": "זהות", + "Invites": "הזמנות", + "Invocation": "הפעלה", + "Join requests": "בקשות הצטרפות", + "Lifecycle": "מחזור חיים", + "Linked Issues": "משימות מקושרות", + "Linked issues": "משימות מקושרות", + "Options": "אפשרויות", + "Output": "פלט", + "Owner": "בעלים", + "Permissions": "הרשאות", + "Properties": "מאפיינים", + "Providers": "ספקים", + "Quick filters": "מסננים מהירים", + "Recent financial events": "אירועי כספים אחרונים", + "Recent operations": "פעולות אחרונות", + "Remaining": "נותר", + "Result": "תוצאה", + "Revoked Keys": "מפתחות שבוטלו", + "Runtime services": "שירותי ריצה", + "Selected skills": "כישורים נבחרים", + "Summary": "סיכום", + "Tokens": "אסימונים", + "Unsaved changes": "שינויים שלא נשמרו", + "Warnings": "אזהרות", + "Workspace context": "הקשר סביבת עבודה", + "Worktree": "עץ עבודה", + + // --- Table/List Headers --- + "Action": "פעולה", + "Agent": "סוכן", + "Branch": "ענף", + "Cost": "עלות", + "Created by": "נוצר על ידי", + "Current": "נוכחי", + "Current state": "מצב נוכחי", + "Date": "תאריך", + "Input tokens": "אסימוני קלט", + "Output tokens": "אסימוני פלט", + "Cached tokens": "אסימונים ממטמון", + "Last Crash": "קריסה אחרונה", + "Last run": "הרצה אחרונה", + "Overall": "כולל", + "Requested by": "התבקש על ידי", + "Task": "משימה", + "Total": "סך הכל", + "Total cost": "עלות כוללת", + "Used by": "בשימוש על ידי", + + // --- Misc UI Labels --- + "About": "אודות", + "Adapter": "מתאם", + "After": "אחרי", + "Before": "לפני", + "Board view": "תצוגת לוח", + "Coming soon": "בקרוב", + "Documentation": "תיעוד", + "Earlier": "מוקדם יותר", + "Estimated": "משוער", + "External": "חיצוני", + "Human": "אנושי", + "My recent issues": "המשימות האחרונות שלי", + "Optional": "אופציונלי", + "Per issue": "למשימה", + "Per run": "להרצה", + "Secret": "סוד", + "See All \u2192": "ראה הכל \u2192", + "Small": "קטן", + "Large": "גדול", + "User": "משתמש", + "View details \u2192": "צפה בפרטים \u2192", + "All approval statuses": "כל סטטוסי האישור", + "All categories": "כל הקטגוריות", + "All providers": "כל הספקים", + "All types": "כל הסוגים", + "All Time": "כל הזמנים", + "Last 14 days": "14 ימים אחרונים", + "Automation enabled.": "אוטומציה מופעלת.", + "Automation paused.": "אוטומציה מושהית.", + "Mobile navigation": "ניווט נייד", + + // --- Command Palette --- + "Pages": "דפים", + + // --- Onboarding --- + "Bootstrap your Paperclip instance": "הקם את מופע ה-Paperclip שלך", + "Name your company": "תן שם לחברה שלך", + "This is the organization your agents will work for.": "זהו הארגון שעבורו הסוכנים שלך יעבדו.", + "This will be the CEO": "זה יהיה המנכ\"ל", + "Give it something to do": "תן לו משהו לעשות", + "Ready to launch": "מוכן להפעלה", + "Welcome to Paperclip. Set up your first company and agent to get started.": "ברוכים הבאים ל-Paperclip. הקם את החברה והסוכן הראשונים שלך כדי להתחיל.", + "I want advanced configuration myself": "אני רוצה להגדיר בעצמי הגדרות מתקדמות", + "Get started by creating a company.": "התחל ביצירת חברה.", + + // --- Descriptions --- + "Choose how this agent will run tasks.": "בחר כיצד סוכן זה יריץ משימות.", + "Choose your adapter type for advanced setup.": "בחר את סוג המתאם להגדרה מתקדמת.", + "Plugins are alpha.": "התוספים בשלב אלפא.", + "Read our terms of service": "קרא את תנאי השירות שלנו", + "Select a company first.": "בחר חברה תחילה.", + "Sharing is currently disabled.": "השיתוף מושבת כרגע.", + "Don't allow": "אל תאפשר", + "Always allow": "אפשר תמיד", + "What could have been better?": "מה יכול היה להיות טוב יותר?", + "Drop image to upload": "גרור תמונה להעלאה", + + // --- Aria Labels --- + "breadcrumb": "ניווט", + "toggle menu": "החלפת תפריט", + "Instance settings": "הגדרות מופע", + "Scroll to bottom": "גלול למטה", + "Change project color": "שנה צבע פרויקט", + "Zoom in": "הגדל", + "Zoom out": "הקטן", + "Fit chart to screen": "התאם תרשים למסך" + }; + + // ========================================================================= + // TRANSLATION ENGINE + // ========================================================================= + + // Build a reverse lookup for performance: lowercase -> original key + const lowerMap = {}; + for (const key in T) { + lowerMap[key.toLowerCase()] = key; + } + + /** + * Translate a text string if a translation exists. + * Uses exact match first, then case-insensitive. + */ + function translate(text) { + const trimmed = text.trim(); + if (!trimmed) return null; + // Exact match + if (T[trimmed] !== undefined) return T[trimmed]; + // Case-insensitive match + const lk = trimmed.toLowerCase(); + if (lowerMap[lk]) return T[lowerMap[lk]]; + return null; + } + + /** + * Check if a node is likely user-generated content (should NOT be translated). + * This includes agent names, issue titles in dynamic lists, etc. + */ + function isUserContent(node) { + let el = node.parentElement; + while (el) { + // Skip elements with contenteditable (editors) + if (el.contentEditable === "true") return true; + // Skip code blocks + const tag = el.tagName; + if (tag === "CODE" || tag === "PRE") return true; + // Skip markdown rendered content body (but not headings/labels) + if (el.classList && el.classList.contains("paperclip-markdown")) return true; + // Skip elements explicitly marked as user content + if (el.dataset && el.dataset.noTranslate === "true") return true; + el = el.parentElement; + } + return false; + } + + /** + * Walk all text nodes under a root and translate them. + */ + function translateTextNodes(root) { + const walker = document.createTreeWalker( + root, + NodeFilter.SHOW_TEXT, + null + ); + let node; + while ((node = walker.nextNode())) { + if (isUserContent(node)) continue; + const original = node.nodeValue; + const translated = translate(original); + if (translated !== null && translated !== original) { + node.nodeValue = translated; + } + } + } + + /** + * Translate placeholder and aria-label attributes. + */ + function translateAttributes(root) { + // Placeholders + const inputs = root.querySelectorAll("input[placeholder], textarea[placeholder]"); + inputs.forEach(function (el) { + const t = translate(el.placeholder); + if (t) el.placeholder = t; + }); + + // aria-labels + const ariaEls = root.querySelectorAll("[aria-label]"); + ariaEls.forEach(function (el) { + const t = translate(el.getAttribute("aria-label")); + if (t) el.setAttribute("aria-label", t); + }); + + // title attributes + const titleEls = root.querySelectorAll("[title]"); + titleEls.forEach(function (el) { + const t = translate(el.getAttribute("title")); + if (t) el.setAttribute("title", t); + }); + } + + /** + * Full translation pass on the entire document. + */ + function translateAll() { + translateTextNodes(document.body); + translateAttributes(document.body); + // Translate page title + if (document.title) { + const t = translate(document.title); + if (t) document.title = t; + } + } + + // ========================================================================= + // MUTATION OBSERVER - catch route changes and dynamic content + // ========================================================================= + let translateTimer = null; + + function scheduleTranslation() { + if (translateTimer) return; + translateTimer = setTimeout(function () { + translateTimer = null; + translateAll(); + }, 50); + } + + // Observe #root for changes (React renders here) + function startObserver() { + const root = document.getElementById("root"); + if (!root) return; + + const observer = new MutationObserver(function (mutations) { + let hasTextChange = false; + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i]; + if (m.type === "childList" && m.addedNodes.length > 0) { + hasTextChange = true; + break; + } + if (m.type === "characterData") { + hasTextChange = true; + break; + } + } + if (hasTextChange) { + scheduleTranslation(); + } + }); + + observer.observe(root, { + childList: true, + subtree: true, + characterData: true + }); + + // Also observe body for Radix portals (dialogs, tooltips, dropdowns) + const bodyObserver = new MutationObserver(function (mutations) { + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i]; + if (m.type === "childList") { + for (let j = 0; j < m.addedNodes.length; j++) { + const node = m.addedNodes[j]; + if (node.nodeType === 1 && node !== root) { + translateTextNodes(node); + translateAttributes(node); + } + } + } + } + }); + + bodyObserver.observe(document.body, { + childList: true, + subtree: false + }); + } + + // ========================================================================= + // INIT + // ========================================================================= + function init() { + // Wait for React to render + setTimeout(function () { + translateAll(); + startObserver(); + }, 300); + + // Additional pass after longer delay for lazy-loaded content + setTimeout(translateAll, 1500); + setTimeout(translateAll, 4000); + } + + // Start when DOM is ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/patch-html.sh b/patch-html.sh index 087ef73..8a9b7de 100644 --- a/patch-html.sh +++ b/patch-html.sh @@ -30,4 +30,4 @@ echo "Hebrew RTL patch applied successfully" echo "Patched files:" echo " - $UI_DIR/index.html" echo " - $UI_DIR/assets/rtl-override.css" -echo " - $UI_DIR/assets/translate-he.js" \ No newline at end of file +echo " - $UI_DIR/assets/translate-he.js"