@@ -10,121 +10,131 @@ RuleTester.it = it
1010const ruleTester = new RuleTester ( {
1111 languageOptions : {
1212 parserOptions : {
13- ecmaVersion : 2022 ,
14- sourceType : "module" ,
15- ecmaFeatures : {
16- jsx : true
17- }
13+ projectService : {
14+ allowDefaultProject : [ "*.ts" , "*.tsx" ]
15+ } ,
16+ tsconfigRootDir : import . meta. dirname
1817 }
1918 }
2019} )
2120
2221ruleTester . run ( "no-unlocalized-strings" , noUnlocalizedStrings , {
2322 valid : [
2423 // Inside t``
25- "t`Hello World`" ,
26- "t`Save changes`" ,
24+ { code : "t`Hello World`" , filename : "test.tsx" } ,
25+ { code : "t`Save changes`" , filename : "test.tsx" } ,
2726
2827 // Inside <Trans>
29- "<Trans>Hello World</Trans>" ,
30- "<Trans>Save changes</Trans>" ,
28+ { code : "<Trans>Hello World</Trans>" , filename : "test.tsx" } ,
29+ { code : "<Trans>Save changes</Trans>" , filename : "test.tsx" } ,
3130
3231 // Inside msg/defineMessage
33- 'msg({ message: "Hello World" })' ,
34- 'defineMessage({ message: "Save changes" })' ,
32+ { code : 'msg({ message: "Hello World" })' , filename : "test.tsx" } ,
33+ { code : 'defineMessage({ message: "Save changes" })' , filename : "test.tsx" } ,
3534
3635 // Console/debug (default ignored functions)
37- 'console.log("Hello World")' ,
38- 'console.error("Something went wrong")' ,
36+ { code : 'console.log("Hello World")' , filename : "test.tsx" } ,
37+ { code : 'console.error("Something went wrong")' , filename : "test.tsx" } ,
3938
4039 // Ignored properties (className, type, etc.)
41- '<div className="my-class" />' ,
42- '<input type="text" />' ,
43- '<div id="my-id" />' ,
44- '<div data-testid="test-button" />' ,
45- '<a href="/path/to/page" />' ,
40+ { code : '<div className="my-class" />' , filename : "test.tsx" } ,
41+ { code : '<input type="text" />' , filename : "test.tsx" } ,
42+ { code : '<div id="my-id" />' , filename : "test.tsx" } ,
43+ { code : '<div data-testid="test-button" />' , filename : "test.tsx" } ,
44+ { code : '<a href="/path/to/page" />' , filename : "test.tsx" } ,
4645
4746 // Object properties with ignored keys
48- '({ type: "button" })' ,
49- '({ className: "my-class" })' ,
47+ { code : '({ type: "button" })' , filename : "test.tsx" } ,
48+ { code : '({ className: "my-class" })' , filename : "test.tsx" } ,
5049
5150 // Technical strings (no spaces, identifiers)
52- 'const x = "myIdentifier"' ,
53- 'const x = "my-css-class"' ,
54- 'const x = "CONSTANT_VALUE"' ,
51+ { code : 'const x = "myIdentifier"' , filename : "test.tsx" } ,
52+ { code : 'const x = "my-css-class"' , filename : "test.tsx" } ,
53+ { code : 'const x = "CONSTANT_VALUE"' , filename : "test.tsx" } ,
5554
5655 // URLs and paths
57- 'const url = "https://example.com"' ,
58- 'const path = "/api/users"' ,
59- 'const mailto = "mailto:test@example.com"' ,
56+ { code : 'const url = "https://example.com"' , filename : "test.tsx" } ,
57+ { code : 'const path = "/api/users"' , filename : "test.tsx" } ,
58+ { code : 'const mailto = "mailto:test@example.com"' , filename : "test.tsx" } ,
6059
6160 // Single characters
62- 'const sep = "-"' ,
63- 'const x = "."' ,
61+ { code : 'const sep = "-"' , filename : "test.tsx" } ,
62+ { code : 'const x = "."' , filename : "test.tsx" } ,
6463
6564 // Empty strings
66- 'const x = ""' ,
67- 'const x = " "' ,
65+ { code : 'const x = ""' , filename : "test.tsx" } ,
66+ { code : 'const x = " "' , filename : "test.tsx" } ,
6867
6968 // Type contexts
70- 'type Status = "loading" | "error"' ,
71- "interface Props { variant: 'primary' | 'secondary' }" ,
72-
73- // Native Intl methods - locale strings
74- 'date.toLocaleDateString("de-DE")' ,
75- 'date.toLocaleTimeString("en-US")' ,
76- 'number.toLocaleString("de-DE")' ,
77- '"hello".localeCompare("world", "de")' ,
78-
79- // Native Intl methods - option values
80- 'date.toLocaleDateString("de-DE", { weekday: "long" })' ,
81- 'date.toLocaleDateString("de-DE", { month: "short", year: "numeric" })' ,
82- 'new Intl.DateTimeFormat("en", { dateStyle: "full" })' ,
83- 'new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" })' ,
84- 'new Intl.RelativeTimeFormat("en", { numeric: "auto" })' ,
69+ { code : 'type Status = "loading" | "error"' , filename : "test.tsx" } ,
70+ { code : "interface Props { variant: 'primary' | 'secondary' }" , filename : "test.tsx" } ,
8571
8672 // Ignore pattern
8773 {
8874 code : 'const x = "test_id_123"' ,
75+ filename : "test.tsx" ,
8976 options : [ { ignoreFunctions : [ ] , ignoreProperties : [ ] , ignoreNames : [ ] , ignorePattern : "^test_" } ]
9077 } ,
9178
9279 // Non-UI looking text
93- 'const x = "myFunction"' ,
94- 'const x = "onClick"'
80+ { code : 'const x = "myFunction"' , filename : "test.tsx" } ,
81+ { code : 'const x = "onClick"' , filename : "test.tsx" } ,
82+
83+ // TypeScript: String literal union types are recognized as technical
84+ {
85+ code : `
86+ type Align = "left" | "center" | "right"
87+ const align: Align = "center"
88+ ` ,
89+ filename : "test.tsx"
90+ } ,
91+ {
92+ code : `
93+ function setMode(mode: "dark" | "light") {}
94+ setMode("dark")
95+ ` ,
96+ filename : "test.tsx"
97+ }
9598 ] ,
9699 invalid : [
97100 // Plain string that looks like UI text
98101 {
99102 code : 'const label = "Save changes"' ,
103+ filename : "test.tsx" ,
100104 errors : [ { messageId : "unlocalizedString" } ]
101105 } ,
102106 {
103107 code : 'const msg = "Something went wrong!"' ,
108+ filename : "test.tsx" ,
104109 errors : [ { messageId : "unlocalizedString" } ]
105110 } ,
106111 {
107112 code : 'const title = "Welcome to the app"' ,
113+ filename : "test.tsx" ,
108114 errors : [ { messageId : "unlocalizedString" } ]
109115 } ,
110116
111117 // JSX text
112118 {
113119 code : "<button>Save changes</button>" ,
120+ filename : "test.tsx" ,
114121 errors : [ { messageId : "unlocalizedString" } ]
115122 } ,
116123 {
117124 code : "<div>Something went wrong!</div>" ,
125+ filename : "test.tsx" ,
118126 errors : [ { messageId : "unlocalizedString" } ]
119127 } ,
120128 {
121129 code : "<p>Please try again.</p>" ,
130+ filename : "test.tsx" ,
122131 errors : [ { messageId : "unlocalizedString" } ]
123132 } ,
124133
125- // JSX with title/aria-label that's not in default ignore list
134+ // JSX with title ( not in default ignore list)
126135 {
127136 code : '<button title="Click here">X</button>' ,
137+ filename : "test.tsx" ,
128138 errors : [ { messageId : "unlocalizedString" } ]
129139 } ,
130140
@@ -134,27 +144,27 @@ ruleTester.run("no-unlocalized-strings", noUnlocalizedStrings, {
134144 const a = "Hello World"
135145 const b = "Goodbye World"
136146 ` ,
137- errors : [
138- { messageId : "unlocalizedString" } ,
139- { messageId : "unlocalizedString" }
140- ]
147+ filename : "test.tsx" ,
148+ errors : [ { messageId : "unlocalizedString" } , { messageId : "unlocalizedString" } ]
141149 } ,
142150
143151 // Function return value
144152 {
145153 code : 'function getLabel() { return "Click me" }' ,
154+ filename : "test.tsx" ,
146155 errors : [ { messageId : "unlocalizedString" } ]
147156 } ,
148157
149158 // Object property (non-ignored key)
150159 {
151160 code : '({ label: "Save changes" })' ,
161+ filename : "test.tsx" ,
152162 errors : [ { messageId : "unlocalizedString" } ]
153163 } ,
154164 {
155165 code : '({ message: "Error occurred!" })' ,
166+ filename : "test.tsx" ,
156167 errors : [ { messageId : "unlocalizedString" } ]
157168 }
158169 ]
159170} )
160-
0 commit comments