11let isPickerEnabled = false ;
22let hoveredElement = null ;
33
4- // The main listener for messages from the popup
54chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
65 if ( message . type === "ENABLE_PICKER" ) {
76 if ( ! isPickerEnabled ) enablePicker ( ) ;
@@ -35,45 +34,193 @@ function onMouseOut(e) {
3534 hoveredElement = null ;
3635}
3736
38- function onClick ( e ) {
37+ async function onClick ( e ) {
3938 e . preventDefault ( ) ;
4039 e . stopPropagation ( ) ;
4140
4241 const clickedElement = e . target ;
4342 const selectedCode = clickedElement . textContent ;
43+ const cacheKey = `translation_${ hashCode ( selectedCode ) } ` ;
44+ const originalWidth = clickedElement . getBoundingClientRect ( ) . width ;
4445 disablePicker ( ) ;
4546
47+ const { targetLanguage } = await chrome . storage . sync . get ( 'targetLanguage' ) ;
48+ const lang = targetLanguage || 'Java' ;
49+
50+ const cachedData = await getFromCache ( cacheKey ) ;
51+ if ( cachedData && cachedData [ lang ] ) {
52+ injectOrUpdateTranslations ( cachedData , clickedElement , originalWidth ) ;
53+ return ;
54+ }
55+
4656 const loadingDiv = document . createElement ( 'div' ) ;
4757 loadingDiv . className = 'translator-loading' ;
48- loadingDiv . textContent = ' Translating...' ;
58+ loadingDiv . textContent = ` Translating to ${ lang } ...` ;
4959 clickedElement . parentNode . insertBefore ( loadingDiv , clickedElement . nextSibling ) ;
5060
51- chrome . runtime . sendMessage ( { type : "TRANSLATE_CODE" , code : selectedCode } , ( response ) => {
61+ chrome . runtime . sendMessage ( { type : "TRANSLATE_CODE" , code : selectedCode } , async ( response ) => {
5262 loadingDiv . remove ( ) ;
5363
5464 if ( response . error ) {
5565 alert ( `Error: ${ response . error } ` ) ;
5666 } else if ( response . translation ) {
57- injectTranslatedCode ( response . translation , clickedElement ) ;
67+ const cleanedTranslation = response . translation . replace ( / ` ` ` [ a - z ] * \n / g, '' ) . replace ( / ` ` ` / g, '' ) . trim ( ) ;
68+
69+ const newData = cachedData || { } ;
70+ newData [ lang ] = cleanedTranslation ;
71+ await saveToCache ( cacheKey , newData , 10 ) ;
72+
73+ injectOrUpdateTranslations ( newData , clickedElement , originalWidth ) ;
74+ }
75+ } ) ;
76+ }
77+
78+ function injectOrUpdateTranslations ( translations , originalElement , width ) {
79+ const componentStyles = `
80+ .tab-nav {
81+ display: flex;
82+ border-bottom: 1px solid #ccc;
83+ background-color: #f0f0f0;
84+ }
85+ .tab-link {
86+ padding: 10px 15px;
87+ cursor: pointer;
88+ border: none;
89+ background-color: transparent;
90+ font-size: 1em;
91+ font-weight: 500;
92+ color: #333;
93+ border-bottom: 3px solid transparent;
94+ }
95+ .tab-link:hover {
96+ background-color: #e5e5e5;
97+ }
98+ .tab-link.active {
99+ color: #007bff;
100+ border-bottom: 3px solid #007bff;
101+ }
102+ .tab-content-area {
103+ background-color: #fff;
104+ }
105+ .tab-content {
106+ display: none;
107+ }
108+ .tab-content.active {
109+ display: block;
110+ }
111+ pre {
112+ margin: 0;
113+ padding: 16px;
114+ white-space: pre-wrap;
115+ word-wrap: break-word;
116+ background-color: #f6f8fa;
117+ border-top: 1px solid #ddd;
118+ margin-bottom: 10px;
119+ }
120+ code {
121+ font-family: monospace;
122+ font-size: 0.65em;
123+ color: #24292e;
124+ line-height: 1;
125+ }
126+ ` ;
127+
128+ let container = originalElement . nextElementSibling ;
129+ if ( ! container || container . id !== 'my-code-translator-container' ) {
130+ container = document . createElement ( 'div' ) ;
131+ container . id = 'my-code-translator-container' ;
132+
133+ const shadowRoot = container . attachShadow ( { mode : 'open' } ) ;
134+
135+ const styleElement = document . createElement ( 'style' ) ;
136+ styleElement . textContent = componentStyles ;
137+ shadowRoot . appendChild ( styleElement ) ;
138+
139+ const uiWrapper = document . createElement ( 'div' ) ;
140+ shadowRoot . appendChild ( uiWrapper ) ;
141+
142+ originalElement . parentNode . insertBefore ( container , originalElement . nextSibling ) ;
143+ }
144+
145+ container . style . width = `${ width } px` ;
146+ container . style . boxSizing = 'border-box' ;
147+
148+ const shadowRoot = container . shadowRoot ;
149+ const uiWrapper = shadowRoot . querySelector ( 'div' ) ;
150+ uiWrapper . innerHTML = '' ;
151+
152+ const tabNav = document . createElement ( 'div' ) ;
153+ tabNav . className = 'tab-nav' ;
154+
155+ const contentArea = document . createElement ( 'div' ) ;
156+ contentArea . className = 'tab-content-area' ;
157+
158+ uiWrapper . appendChild ( tabNav ) ;
159+ uiWrapper . appendChild ( contentArea ) ;
160+
161+ Object . keys ( translations ) . forEach ( ( lang , index ) => {
162+ const tabButton = document . createElement ( 'button' ) ;
163+ tabButton . className = 'tab-link' ;
164+ tabButton . textContent = lang ;
165+
166+ const contentPanel = document . createElement ( 'div' ) ;
167+ contentPanel . className = 'tab-content' ;
168+
169+ const pre = document . createElement ( 'pre' ) ;
170+ const code = document . createElement ( 'code' ) ;
171+ code . textContent = translations [ lang ] ;
172+ pre . appendChild ( code ) ;
173+ contentPanel . appendChild ( pre ) ;
174+
175+ tabNav . appendChild ( tabButton ) ;
176+ contentArea . appendChild ( contentPanel ) ;
177+
178+ if ( index === 0 ) {
179+ tabButton . classList . add ( 'active' ) ;
180+ contentPanel . classList . add ( 'active' ) ;
58181 }
182+
183+ tabButton . addEventListener ( 'click' , ( ) => {
184+ shadowRoot . querySelectorAll ( '.tab-link' ) . forEach ( btn => btn . classList . remove ( 'active' ) ) ;
185+ shadowRoot . querySelectorAll ( '.tab-content' ) . forEach ( panel => panel . classList . remove ( 'active' ) ) ;
186+
187+ tabButton . classList . add ( 'active' ) ;
188+ contentPanel . classList . add ( 'active' ) ;
189+ } ) ;
59190 } ) ;
60191}
61192
62- function injectTranslatedCode ( translatedCode , originalElement ) {
63- const container = document . createElement ( 'div' ) ;
64- container . className = 'translation-container' ;
193+ function hashCode ( str ) {
194+ let hash = 0 ;
195+ for ( let i = 0 , len = str . length ; i < len ; i ++ ) {
196+ let chr = str . charCodeAt ( i ) ;
197+ hash = ( hash << 5 ) - hash + chr ;
198+ hash |= 0 ;
199+ }
200+ return hash ;
201+ }
202+
203+ async function saveToCache ( key , data , daysToExpire ) {
204+ const expirationMs = daysToExpire * 24 * 60 * 60 * 1000 ;
205+ const cacheItem = {
206+ data : data ,
207+ expiresAt : Date . now ( ) + expirationMs ,
208+ } ;
209+ await chrome . storage . local . set ( { [ key ] : cacheItem } ) ;
210+ }
65211
66- const header = document . createElement ( 'h4' ) ;
67- header . textContent = 'AI-Generated Translation:' ;
212+ async function getFromCache ( key ) {
213+ const result = await chrome . storage . local . get ( key ) ;
214+ const cacheItem = result [ key ] ;
68215
69- const pre = document . createElement ( 'pre' ) ;
70- const code = document . createElement ( 'code' ) ;
71- code . textContent = translatedCode ;
72- pre . appendChild ( code ) ;
216+ if ( ! cacheItem ) {
217+ return null ;
218+ }
73219
74- container . appendChild ( header ) ;
75- container . appendChild ( pre ) ;
220+ if ( Date . now ( ) > cacheItem . expiresAt ) {
221+ await chrome . storage . local . remove ( key ) ;
222+ return null ;
223+ }
76224
77- // Insert the container right after the original element
78- originalElement . parentNode . insertBefore ( container , originalElement . nextSibling ) ;
225+ return cacheItem . data ;
79226}
0 commit comments