22 * @file src/renderer/components/rules/RuleEditor.tsx
33 *
44 * @created 07.03.2026
5- * @modified 08 .03.2026
5+ * @modified 16 .03.2026
66 *
77 * @author Christian Blank <aidrelay@proton.me>
88 * @copyright 2026
1414 * the rules Zustand store.
1515 */
1616
17- import { useState , useCallback } from 'react'
17+ import { useState , useCallback , useEffect } from 'react'
1818import { X } from 'lucide-react'
1919import { toast } from 'sonner'
20+ import { useTranslation } from 'react-i18next'
2021import { cn } from '@/lib/utils'
2122import { Button } from '@/components/ui/button'
2223import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs'
@@ -51,18 +52,27 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
5152 const [ activeTab , setActiveTab ] = useState < Tab > ( 'details' )
5253 const [ saving , setSaving ] = useState ( false )
5354 const [ content , setContent ] = useState ( rule ?. content ?? '' )
55+ const { t } = useTranslation ( )
5456
5557 const { create, update } = useRulesStore ( )
5658 const tokenEstimate = useTokenEstimate ( content )
5759
60+ useEffect ( ( ) => {
61+ const handleKeyDown = ( event : KeyboardEvent ) => {
62+ if ( event . key === 'Escape' ) onClose ( )
63+ }
64+ document . addEventListener ( 'keydown' , handleKeyDown )
65+ return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown )
66+ } , [ onClose ] )
67+
5868 const handleContentChange = useCallback ( ( newContent : string ) => {
5969 setContent ( newContent )
6070 } , [ ] )
6171
6272 const handleDetailsSubmit = useCallback (
6373 async ( data : Omit < CreateRuleInput , 'content' > ) => {
6474 if ( ! content . trim ( ) ) {
65- toast . error ( 'Rule content cannot be empty' )
75+ toast . error ( t ( 'ruleEditor.contentRequired' ) )
6676 return
6777 }
6878
@@ -87,7 +97,7 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
8797 setSaving ( false )
8898 }
8999 } ,
90- [ content , rule , create , update , onClose ] ,
100+ [ content , rule , create , update , onClose , t ] ,
91101 )
92102
93103 return (
@@ -105,13 +115,21 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
105115 className = "fixed inset-y-0 right-0 z-50 w-full max-w-2xl flex flex-col bg-background border-l border-border shadow-xl"
106116 role = "dialog"
107117 aria-modal = "true"
108- aria-label = { rule ? `Edit rule: ${ rule . name } ` : 'Add rule' }
118+ aria-label = {
119+ rule
120+ ? t ( 'ruleEditor.editTitleWithName' , { name : rule . name } )
121+ : t ( 'ruleEditor.createTitle' )
122+ }
109123 data-testid = "rule-editor"
110124 >
111125 { /* Header */ }
112126 < header className = "flex items-center justify-between px-6 py-4 border-b border-border" >
113127 < div className = "flex items-center gap-3" >
114- < h2 className = "font-semibold text-base" > { rule ? `Edit: ${ rule . name } ` : 'Add rule' } </ h2 >
128+ < h2 className = "font-semibold text-base" >
129+ { rule
130+ ? t ( 'ruleEditor.editTitleWithName' , { name : rule . name } )
131+ : t ( 'ruleEditor.createTitle' ) }
132+ </ h2 >
115133 { tokenEstimate > 0 && (
116134 < span
117135 className = { cn (
@@ -131,13 +149,13 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
131149 variant = "ghost"
132150 size = "icon"
133151 onClick = { onClose }
134- aria-label = "Close editor"
152+ aria-label = { t ( 'ruleEditor.close' ) }
135153 data-testid = "rule-editor-close"
136154 >
137155 < X size = { 18 } />
138156 </ Button >
139157 </ TooltipTrigger >
140- < TooltipContent > Close </ TooltipContent >
158+ < TooltipContent > { t ( 'ruleEditor.close' ) } </ TooltipContent >
141159 </ Tooltip >
142160 </ header >
143161
@@ -156,14 +174,14 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
156174 className = "rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
157175 data-testid = "rule-editor-tab-details"
158176 >
159- Details
177+ { t ( 'ruleEditor.tabDetails' ) }
160178 </ TabsTrigger >
161179 < TabsTrigger
162180 value = "content"
163181 className = "rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
164182 data-testid = "rule-editor-tab-content"
165183 >
166- Content
184+ { t ( 'ruleEditor.tabContent' ) }
167185 </ TabsTrigger >
168186 </ TabsList >
169187 < TabsContent value = "details" className = "flex-1 overflow-y-auto px-6 py-5 mt-0" >
@@ -190,14 +208,14 @@ const RuleEditor = ({ rule, onClose }: RuleEditorProps) => {
190208 onClick = { onClose }
191209 data-testid = "rule-editor-content-cancel"
192210 >
193- Cancel
211+ { t ( 'ruleEditor.cancel' ) }
194212 </ Button >
195213 < Button
196214 type = "button"
197215 onClick = { ( ) => setActiveTab ( 'details' ) }
198216 data-testid = "rule-editor-content-next"
199217 >
200- { rule ? 'Save changes' : 'Continue to details →' }
218+ { t ( 'ruleEditor.continueToDetails' ) }
201219 </ Button >
202220 </ footer >
203221 ) }
0 commit comments