Files
legal-ai/scripts/paperclip-assets/translate-he.js
Chaim 11c73a7c60 CEO: add email notifications, subtask parentId, and Paperclip UI assets
- CEO agent now sends email via notify.py when awaiting human response
- CEO creates child issues (parentId) instead of flat disconnected issues
- Fix notify.py email address to chaim+paperclip@marcus-law.co.il
- Move Paperclip UI assets (RTL CSS + Hebrew JS) into repo under scripts/
- Add deploy.sh script to push assets to live Paperclip instance
- Fix comment box positioning: newest comment on top, input below it

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:55:55 +00:00

1352 lines
54 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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": "בחר",
// --- Activity Log ---
"updated this task": "עדכן משימה זו",
"created this task": "יצר משימה זו",
"created this issue": "יצר משימה זו",
"updated this issue": "עדכן משימה זו",
"commented on this task": "הגיב על משימה זו",
"commented on this issue": "הגיב על משימה זו",
"assigned this task": "הקצה משימה זו",
"assigned this issue": "הקצה משימה זו",
"changed status": "שינה סטטוס",
"changed priority": "שינה עדיפות",
"unassigned this task": "ביטל הקצאת משימה",
"checked out this task": "לקח משימה",
"released this task": "שחרר משימה",
"just now": "הרגע",
"1m ago": "לפני דקה",
"2m ago": "לפני 2 דקות",
"3m ago": "לפני 3 דקות",
"5m ago": "לפני 5 דקות",
"10m ago": "לפני 10 דקות",
"15m ago": "לפני 15 דקות",
"20m ago": "לפני 20 דקות",
"25m ago": "לפני 25 דקות",
"28m ago": "לפני 28 דקות",
"30m ago": "לפני 30 דקות",
"45m ago": "לפני 45 דקות",
"1h ago": "לפני שעה",
"2h ago": "לפני שעתיים",
"3h ago": "לפני 3 שעות",
"yesterday": "אתמול",
"You": "אני",
"Copy as markdown": "העתק כ-markdown",
// --- Run Statuses ---
"succeeded": "הצליח",
"failed": "נכשל",
"running": "רץ",
"timed_out": "חרג מזמן",
"queued": "בתור",
"skipped": "דולג",
"blocked": "חסום",
"completed": "הושלם",
"cancelled": "בוטל",
"started": "התחיל",
// --- Activity Log Actions ---
"started a run": "התחיל ריצה",
"completed a run": "השלים ריצה",
"checked out": "לקח לטיפול",
"released": "שחרר",
"moved to": "עבר ל",
"changed status to": "שינה סטטוס ל",
"assigned to": "הוקצה ל",
"unassigned from": "בוטלה הקצאה מ",
"added a comment": "הוסיף תגובה",
"ran": "הריץ",
"set priority": "קבע עדיפות",
"set status": "קבע סטטוס",
// --- Run Details ---
"Transcript": "תמליל",
"No transcript captured": "לא נקלט תמליל",
"No transcript captured.": "לא נקלט תמליל.",
"Run details": "פרטי ריצה",
"Duration": "משך",
"Tokens": "טוקנים",
"Cost": "עלות",
"Model": "מודל",
"Turns": "סיבובים",
"Exit code": "קוד יציאה",
"Error": "שגיאה",
"Heartbeat": "פעימה",
"Heartbeats": "פעימות",
"Latest run": "ריצה אחרונה",
"All runs": "כל הריצות",
"No runs yet": "אין ריצות עדיין",
"No runs yet.": "אין ריצות עדיין.",
"Run": "ריצה",
"Runs": "ריצות",
"Input tokens": "טוקני קלט",
"Output tokens": "טוקני פלט",
"Cache read": "קריאה ממטמון",
"Cache write": "כתיבה למטמון",
"Total cost": "עלות כוללת",
"Session": "סשן",
"Agent": "סוכן",
"Source": "מקור",
"Trigger": "טריגר",
"on_demand": "לפי דרישה",
"manual": "ידני",
"timer": "טיימר",
"assignment": "הקצאה",
"automation": "אוטומציה",
// --- Filter/Context ---
"For": "עבור",
"for": "עבור",
"in": "ב",
"In": "ב",
"of": "של",
"to": "ל",
"by": "על ידי",
"from": "מ",
"with": "עם",
"and": "ו",
"or": "או",
"on": "ב",
"at": "ב",
"as": "בתור",
"via": "דרך",
"No results": "אין תוצאות",
"No results.": "אין תוצאות.",
"No items": "אין פריטים",
"No data": "אין נתונים",
// --- Settings Page ---
"General Settings": "הגדרות כלליות",
"Server Configuration": "הגדרות שרת",
"Deployment Mode": "מצב פריסה",
"Exposure": "חשיפה",
"Allowed Hostnames": "שמות מארח מורשים",
"Public Base URL": "כתובת URL ציבורית",
"Disable Sign Up": "ביטול הרשמה",
"Disable sign up": "ביטול הרשמה",
"Backup Settings": "הגדרות גיבוי",
"Backup Interval": "מרווח גיבוי",
"Retention Days": "ימי שמירה",
"Database Mode": "מצב מסד נתונים",
"Embedded PostgreSQL": "PostgreSQL מובנה",
"Storage Provider": "ספק אחסון",
"Local Disk": "דיסק מקומי",
"Secret Provider": "ספק סודות",
"Logging Mode": "מצב לוגים",
"Log Directory": "תיקיית לוגים",
"Save Changes": "שמור שינויים",
"Configuration saved": "ההגדרות נשמרו",
"Configuration saved.": "ההגדרות נשמרו.",
"Restart required": "נדרש הפעלה מחדש",
"Instance": "מופע",
"Server": "שרת",
"Auth": "אימות",
"Authentication": "אימות",
"Sign-up": "הרשמה",
"Allowed": "מורשה",
"Port": "פורט",
"Host": "מארח",
"public": "ציבורי",
"private": "פרטי",
"authenticated": "מאומת",
"enabled": "מופעל",
"disabled": "מושבת",
"Database": "מסד נתונים",
"Backups": "גיבויים",
"Storage": "אחסון",
"Secrets": "סודות",
"Logging": "לוגים",
"Server port": "פורט שרת",
"Listen host": "מארח האזנה",
"Base URL": "כתובת בסיס",
"Interval (minutes)": "מרווח (דקות)",
"Retention (days)": "שמירה (ימים)",
"Data directory": "תיקיית נתונים",
"Backup directory": "תיקיית גיבוי",
"Key file path": "נתיב קובץ מפתח",
"Mode": "מצב",
"Provider": "ספק",
"Path": "נתיב",
"Danger Zone": "אזור סכנה",
// --- Experimental Page ---
"Opt into features that are still being evaluated before they become default behavior.": "הצטרף לפיצ׳רים שעדיין בהערכה לפני שהם הופכים לברירת מחדל.",
"Enable Isolated Workspaces": "הפעל סביבות עבודה מבודדות",
"Show execution workspace controls in project configuration and allow isolated workspace behavior for new and existing issue runs.": "הצג אפשרויות סביבת עבודה בהגדרות פרויקט ואפשר התנהגות מבודדת לריצות חדשות וקיימות.",
"Auto-Restart Dev Server When Idle": "הפעלה מחדש אוטומטית של שרת פיתוח בזמן חוסר פעילות",
"In `pnpm dev:once`, wait for all queued and running local agent runs to finish, then restart the server automatically when backend changes or migrations make the current boot stale.": "ב-pnpm dev:once, המתן לסיום כל ריצות הסוכנים בתור וברצף, ואז הפעל מחדש אוטומטית כששינויים בשרת הופכים את האתחול הנוכחי למיושן.",
// --- Plugin Status Page ---
"Runtime Dashboard": "לוח בקרה תפעולי",
"Worker process, scheduled jobs, and webhook deliveries": "תהליך עובד, משימות מתוזמנות ומשלוחי webhook",
"Worker Process": "תהליך עובד",
"Pending RPCs": "קריאות ממתינות",
"Uptime": "זמן פעילות",
"Recent Job Runs": "ריצות אחרונות",
"Recent Webhook Deliveries": "משלוחי webhook אחרונים",
"No webhook deliveries recorded yet.": "טרם נרשמו משלוחי webhook.",
"Last checked": "בדיקה אחרונה",
"Health Status": "מצב בריאות",
"Overall": "כולל",
"ready": "מוכן",
"error": "שגיאה",
"registry": "רישום",
"manifest": "מניפסט",
"error_state": "מצב שגיאה",
"Permissions": "הרשאות",
"Details": "פרטים",
"Plugin ID": "מזהה תוסף",
"Plugin Key": "מפתח תוסף",
"NPM Package": "חבילת NPM",
"Version": "גרסה",
"Configuration": "הגדרות",
"Status": "סטטוס",
// --- Action Buttons ---
"Disable All": "השבת הכל",
"Enable All": "הפעל הכל",
"Disable all": "השבת הכל",
"Enable all": "הפעל הכל",
"Select All": "בחר הכל",
"Select all": "בחר הכל",
"Deselect All": "בטל בחירה",
"Deselect all": "בטל בחירה",
"Clear All": "נקה הכל",
"Clear all": "נקה הכל",
"Remove All": "הסר הכל",
"Remove all": "הסר הכל",
"Delete All": "מחק הכל",
"Delete all": "מחק הכל",
"Refresh": "רענן",
"Reload": "טען מחדש",
"Load More": "טען עוד",
"Load more": "טען עוד",
"Show More": "הצג עוד",
"Show more": "הצג עוד",
"Show Less": "הצג פחות",
"Show less": "הצג פחות",
"View All": "הצג הכל",
"View all": "הצג הכל",
"Expand All": "הרחב הכל",
"Expand all": "הרחב הכל",
"Collapse All": "צמצם הכל",
"Collapse all": "צמצם הכל",
"Copy ID": "העתק מזהה",
"Copy URL": "העתק כתובת",
"Go to": "עבור אל",
"Jump to": "קפוץ אל",
"Mark all": "סמן הכל",
"Danger zone": "אזור סכנה",
"Reset": "איפוס",
"Reset instance": "איפוס מופע",
"This action cannot be undone.": "לא ניתן לבטל פעולה זו.",
"Are you sure?": "האם אתה בטוח?",
// --- Settings: General Tab ---
"Configure instance-wide defaults that affect how operator-visible logs are displayed.": "הגדר ברירות מחדל ברמת המופע שמשפיעות על אופן הצגת הלוגים.",
"Censor username in logs": "הסתר שם משתמש בלוגים",
"Hide the username segment in home-directory paths and similar operator-visible log output. Standalone username mentions outside of paths are not yet masked in the live transcript view. This is off by default.": "הסתר את שם המשתמש בנתיבי תיקיות ובפלט לוגים. אזכורי שם משתמש עצמאיים מחוץ לנתיבים עדיין אינם מוסתרים בתצוגת התמליל החי. מושבת כברירת מחדל.",
// --- Settings: Keyboard Shortcuts ---
"Keyboard Shortcuts": "קיצורי מקלדת",
"Enable app keyboard shortcuts, including inbox navigation and global shortcuts like creating issues or toggling panels. This is off by default.": "הפעל קיצורי מקלדת באפליקציה, כולל ניווט בתיבת הדואר וקיצורים גלובליים כמו יצירת משימות או מעבר בין פנלים. מושבת כברירת מחדל.",
// --- Settings: AI Feedback ---
"AI Feedback Sharing": "שיתוף משוב AI",
"Control whether thumbs up and thumbs down votes can send the voted AI output to Paperclip Labs. Votes are always saved locally.": "קבע אם הצבעות אגודל למעלה ולמטה ישלחו את פלט ה-AI ל-Paperclip Labs. ההצבעות תמיד נשמרות מקומית.",
"Read our Terms of Service": "קרא את תנאי השירות שלנו",
"No default is saved yet. The next thumbs up or thumbs down choice will ask once and then save the answer here.": "טרם נשמרה בחירת ברירת מחדל. ההצבעה הבאה תשאל פעם אחת ותשמור את התשובה כאן.",
"Always allow": "אפשר תמיד",
"Always Allow": "אפשר תמיד",
"Share voted AI outputs automatically.": "שתף פלטי AI שנבחרו אוטומטית.",
"Never allow": "אל תאפשר",
"Never Allow": "אל תאפשר",
"Keep voted AI outputs local only.": "שמור פלטי AI שנבחרו מקומית בלבד.",
"To retest the first-use prompt in local dev, remove the feedbackDataSharingPreference key from the instance_settings.general JSON row for this instance, or set it back to \"prompt\". Unset and \"prompt\" both mean no default has been chosen yet.": "לבדיקה מחדש של ההנחיה הראשונית, הסר את המפתח feedbackDataSharingPreference מטבלת instance_settings או החזר ל-prompt.",
// --- Tabs & Sections ---
"Sub-issues": "תת-משימות",
"Subissues": "תת-משימות",
"sub-issues": "תת-משימות",
"Work Products": "תוצרי עבודה",
"Linked": "מקושר",
"Related": "קשור",
"Parent": "משימת אב",
"Timeline": "ציר זמן",
"History": "היסטוריה",
"Approvals": "אישורים",
// --- Comments Section ---
"No comments yet": "אין תגובות עדיין",
"No comments yet.": "אין תגובות עדיין.",
"Add a comment": "הוסף תגובה",
"Write a comment": "כתוב תגובה",
"Write a comment...": "כתוב תגובה...",
"Type a comment": "הקלד תגובה",
"Type a comment...": "הקלד תגובה...",
// --- Activity Section ---
"No activity yet": "אין פעילות עדיין",
"No activity yet.": "אין פעילות עדיין.",
// --- Sub-issues Section ---
"No sub-issues": "אין תת-משימות",
"No sub-issues.": "אין תת-משימות.",
"Create sub-issue": "צור תת-משימה",
"Add sub-issue": "הוסף תת-משימה",
// --- 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.": "אין תת-יעדים.",
"Sub-Goals": "תת-יעדים",
"Sub Goal": "תת-יעד",
"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.
*/
// Regex patterns for dynamic time strings
const TIME_PATTERNS = [
[/^(\d+)m ago$/, (m) => `לפני ${m[1]} דקות`],
[/^(\d+)h ago$/, (m) => `לפני ${m[1]} שעות`],
[/^(\d+)d ago$/, (m) => `לפני ${m[1]} ימים`],
[/^(\d+)s ago$/, (m) => `לפני ${m[1]} שניות`],
[/^(\d+) minutes? ago$/, (m) => `לפני ${m[1]} דקות`],
[/^(\d+) hours? ago$/, (m) => `לפני ${m[1]} שעות`],
[/^(\d+) days? ago$/, (m) => `לפני ${m[1]} ימים`],
[/^just now$/, () => "הרגע"],
[/^yesterday$/, () => "אתמול"],
// Tabs with counts
[/^Sub-Goals\s*\((\d+)\)$/, (m) => `תת-יעדים (${m[1]})`],
[/^Projects\s*\((\d+)\)$/, (m) => `פרויקטים (${m[1]})`],
[/^Issues\s*\((\d+)\)$/, (m) => `משימות (${m[1]})`],
[/^Agents\s*\((\d+)\)$/, (m) => `סוכנים (${m[1]})`],
[/^Members\s*\((\d+)\)$/, (m) => `חברים (${m[1]})`],
[/^Comments\s*\((\d+)\)$/, (m) => `תגובות (${m[1]})`],
[/^Documents\s*\((\d+)\)$/, (m) => `מסמכים (${m[1]})`],
[/^Routines\s*\((\d+)\)$/, (m) => `שגרות (${m[1]})`],
[/^Skills\s*\((\d+)\)$/, (m) => `כישורים (${m[1]})`],
[/^Labels\s*\((\d+)\)$/, (m) => `תוויות (${m[1]})`],
];
// Regex patterns for date strings
const DATE_MONTHS = {
"Jan": "ינו׳", "Feb": "פבר׳", "Mar": "מרץ", "Apr": "אפר׳",
"May": "מאי", "Jun": "יוני", "Jul": "יולי", "Aug": "אוג׳",
"Sep": "ספט׳", "Oct": "אוק׳", "Nov": "נוב׳", "Dec": "דצמ׳"
};
function translate(text) {
const trimmed = text.trim().replace(/\s+/g, ' ');
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]];
// Regex time patterns
for (const [pattern, replacer] of TIME_PATTERNS) {
const match = trimmed.match(pattern);
if (match) return replacer(match);
}
// Date format with time: "Apr 7, 2026, 6:02 PM" → "7 אפר׳ 2026, 18:02"
const dateTimeMatch = trimmed.match(/^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d+),\s+(\d{4}),\s+(\d+):(\d+)\s+(AM|PM)$/);
if (dateTimeMatch) {
const [, mon, day, year, hr, min, ampm] = dateTimeMatch;
let hour = parseInt(hr);
if (ampm === "PM" && hour !== 12) hour += 12;
if (ampm === "AM" && hour === 12) hour = 0;
return `${day} ${DATE_MONTHS[mon] || mon} ${year}, ${hour.toString().padStart(2,"0")}:${min}`;
}
// Date format without time: "Apr 7, 2026" → "7 אפר׳ 2026"
const dateMatch = trimmed.match(/^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d+),\s+(\d{4})$/);
if (dateMatch) {
const [, mon, day, year] = dateMatch;
return `${day} ${DATE_MONTHS[mon] || mon} ${year}`;
}
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) {
// Add space when text follows a number in a sibling span (e.g. "0" + "active" → "0 פעיל")
const prev = node.previousSibling;
if (prev && prev.nodeType === 1 && /^\d+$/.test(prev.textContent.trim())) {
node.nodeValue = " " + translated;
} else {
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.
*/
/**
* Translate tab buttons and other elements whose textContent contains
* dynamic patterns like "Sub-Goals (0)" that text node walking misses.
*/
function translateButtonLabels(root) {
const buttons = root.querySelectorAll('button[role="tab"], button[data-slot="tabs-trigger"]');
buttons.forEach(function(btn) {
const text = btn.textContent.trim().replace(/\s+/g, ' ');
const t = translate(text);
if (t && t !== text) {
btn.textContent = t;
}
});
}
function translateAll() {
translateTextNodes(document.body);
translateAttributes(document.body);
translateButtonLabels(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);
}
// =========================================================================
// COMMENT ORDER — newest first
// =========================================================================
function reverseComments() {
// Find comment containers by anchor IDs
const commentAnchors = document.querySelectorAll('[id^="comment-"]');
if (!commentAnchors.length) return;
// Group by parent container
const parents = new Set();
commentAnchors.forEach(el => {
const parent = el.parentElement;
if (parent && !parent.dataset.reversed && parent.children.length > 1) {
parents.add(parent);
}
});
parents.forEach(parent => {
const children = Array.from(parent.children);
children.reverse().forEach(child => parent.appendChild(child));
// Move comment input box right after the newest comment.
// After reverse, the editor (originally last) is now first.
// Find it: it's the child without a comment-* ID.
const firstComment = parent.querySelector('[id^="comment-"]');
if (firstComment) {
// Find all non-comment children (editor/input boxes)
Array.from(parent.children).forEach(child => {
if (!child.id || !child.id.startsWith("comment-")) {
// Insert after the first (newest) comment
firstComment.after(child);
}
});
}
parent.dataset.reversed = "true";
});
}
// Re-run on route changes (SPA)
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// Reset reversed flags on navigation
document.querySelectorAll('[data-reversed]').forEach(el => {
delete el.dataset.reversed;
});
setTimeout(reverseComments, 500);
}
});
function initCommentOrder() {
urlObserver.observe(document.body, { childList: true, subtree: true });
// Also run after each mutation to catch late-loading comments
const commentMutObs = new MutationObserver(() => {
setTimeout(reverseComments, 200);
});
commentMutObs.observe(document.body, { childList: true, subtree: true });
setTimeout(reverseComments, 1000);
}
// Start when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => { init(); initCommentOrder(); });
} else {
init();
initCommentOrder();
}
})();