@@ -206,6 +206,111 @@ describe(":has", () => {
206206 } ) ;
207207} ) ;
208208
209+ describe ( ":lang" , ( ) => {
210+ it ( "should match exact language" , ( ) => {
211+ const dom = parseDocument ( '<div lang="en"><p>hello</p></div>' ) ;
212+ const matches = CSSselect . selectAll < AnyNode , Element > ( ":lang(en)" , dom ) ;
213+ expect ( matches ) . toHaveLength ( 2 ) ;
214+ } ) ;
215+
216+ it ( "should match language prefix" , ( ) => {
217+ const dom = parseDocument ( '<div lang="en-US"><p>hello</p></div>' ) ;
218+ const matches = CSSselect . selectAll < AnyNode , Element > ( ":lang(en)" , dom ) ;
219+ expect ( matches ) . toHaveLength ( 2 ) ;
220+ } ) ;
221+
222+ it ( "should be case-insensitive" , ( ) => {
223+ const dom = parseDocument ( '<div lang="en"><p>hello</p></div>' ) ;
224+ expect (
225+ CSSselect . selectAll < AnyNode , Element > ( ":lang(EN)" , dom ) ,
226+ ) . toHaveLength ( 2 ) ;
227+
228+ const dom2 = parseDocument ( '<div lang="EN-US"><p>hello</p></div>' ) ;
229+ expect (
230+ CSSselect . selectAll < AnyNode , Element > ( ":lang(en)" , dom2 ) ,
231+ ) . toHaveLength ( 2 ) ;
232+ } ) ;
233+
234+ it ( "should inherit from ancestors" , ( ) => {
235+ const dom = parseDocument (
236+ '<div lang="fr"><section><p>bonjour</p></section></div>' ,
237+ ) ;
238+ const matches = CSSselect . selectAll < AnyNode , Element > (
239+ "p:lang(fr)" ,
240+ dom ,
241+ ) ;
242+ expect ( matches ) . toHaveLength ( 1 ) ;
243+ } ) ;
244+
245+ it ( "should not match different languages" , ( ) => {
246+ const dom = parseDocument (
247+ '<div lang="fr"><p>bonjour</p></div><div lang="en"><p>hello</p></div>' ,
248+ ) ;
249+ const matches = CSSselect . selectAll < AnyNode , Element > ( ":lang(en)" , dom ) ;
250+ expect ( matches ) . toHaveLength ( 2 ) ;
251+ } ) ;
252+
253+ it ( "should not match partial non-prefix" , ( ) => {
254+ const dom = parseDocument ( '<div lang="enx"><p>hello</p></div>' ) ;
255+ const matches = CSSselect . selectAll < AnyNode , Element > ( ":lang(en)" , dom ) ;
256+ expect ( matches ) . toHaveLength ( 0 ) ;
257+ } ) ;
258+
259+ it ( "should allow closer ancestor to override" , ( ) => {
260+ const dom = parseDocument (
261+ '<div lang="en"><div lang="fr"><p>bonjour</p></div></div>' ,
262+ ) ;
263+ expect (
264+ CSSselect . selectAll < AnyNode , Element > ( "p:lang(fr)" , dom ) ,
265+ ) . toHaveLength ( 1 ) ;
266+ expect (
267+ CSSselect . selectAll < AnyNode , Element > ( "p:lang(en)" , dom ) ,
268+ ) . toHaveLength ( 0 ) ;
269+ } ) ;
270+
271+ it ( "should support comma-separated language ranges" , ( ) => {
272+ const dom = parseDocument (
273+ '<div lang="en"><p>hello</p></div><div lang="fr"><p>bonjour</p></div><div lang="de"><p>hallo</p></div>' ,
274+ ) ;
275+ const matches = CSSselect . selectAll < AnyNode , Element > (
276+ ":lang(en, fr)" ,
277+ dom ,
278+ ) ;
279+ expect ( matches ) . toHaveLength ( 4 ) ;
280+ } ) ;
281+
282+ it ( "should use extended filtering (RFC 4647)" , ( ) => {
283+ // :lang(de-DE) should match de-Latn-DE (skipping single-char subtags)
284+ const dom = parseDocument (
285+ '<p lang="de-DE">a</p><p lang="de-DE-1996">b</p><p lang="de-Latn-DE">c</p><p lang="de-Latf-DE">d</p><p lang="de-Latn-DE-1996">e</p>' ,
286+ ) ;
287+ const matches = CSSselect . selectAll < AnyNode , Element > (
288+ ":lang(de-DE)" ,
289+ dom ,
290+ ) ;
291+ expect ( matches ) . toHaveLength ( 5 ) ;
292+ } ) ;
293+
294+ it ( "should support wildcard primary subtag" , ( ) => {
295+ const dom = parseDocument (
296+ '<p lang="de-CH">a</p><p lang="it-CH">b</p><p lang="fr-CH">c</p><p lang="fr-FR">d</p>' ,
297+ ) ;
298+ const matches = CSSselect . selectAll < AnyNode , Element > (
299+ ":lang(\\*-CH)" ,
300+ dom ,
301+ ) ;
302+ expect ( matches ) . toHaveLength ( 3 ) ;
303+ } ) ;
304+
305+ it ( "should support xml:lang attribute" , ( ) => {
306+ const dom = parseDocument ( '<div xml:lang="ja"><p>hello</p></div>' , {
307+ xmlMode : true ,
308+ } ) ;
309+ const matches = CSSselect . selectAll < AnyNode , Element > ( ":lang(ja)" , dom ) ;
310+ expect ( matches ) . toHaveLength ( 2 ) ;
311+ } ) ;
312+ } ) ;
313+
209314describe ( ":read-only and :read-write" , ( ) => {
210315 it ( "should match" , ( ) => {
211316 const dom = parseDocument ( `
0 commit comments