diff --git a/lib/rules/template-require-lang-attribute.js b/lib/rules/template-require-lang-attribute.js
index a2c3b31388..fd5ac85185 100644
--- a/lib/rules/template-require-lang-attribute.js
+++ b/lib/rules/template-require-lang-attribute.js
@@ -1,189 +1,4 @@
-// Common valid BCP 47 language tags (not exhaustive, but covers the most common)
-const COMMON_LANG_CODES = new Set([
- 'aa',
- 'ab',
- 'af',
- 'ak',
- 'am',
- 'an',
- 'ar',
- 'as',
- 'av',
- 'ay',
- 'az',
- 'ba',
- 'be',
- 'bg',
- 'bh',
- 'bi',
- 'bm',
- 'bn',
- 'bo',
- 'br',
- 'bs',
- 'ca',
- 'ce',
- 'ch',
- 'co',
- 'cr',
- 'cs',
- 'cu',
- 'cv',
- 'cy',
- 'da',
- 'de',
- 'dv',
- 'dz',
- 'ee',
- 'el',
- 'en',
- 'eo',
- 'es',
- 'et',
- 'eu',
- 'fa',
- 'ff',
- 'fi',
- 'fj',
- 'fo',
- 'fr',
- 'fy',
- 'ga',
- 'gd',
- 'gl',
- 'gn',
- 'gu',
- 'gv',
- 'ha',
- 'he',
- 'hi',
- 'ho',
- 'hr',
- 'ht',
- 'hu',
- 'hy',
- 'hz',
- 'ia',
- 'id',
- 'ie',
- 'ig',
- 'ii',
- 'ik',
- 'io',
- 'is',
- 'it',
- 'iu',
- 'ja',
- 'jv',
- 'ka',
- 'kg',
- 'ki',
- 'kj',
- 'kk',
- 'kl',
- 'km',
- 'kn',
- 'ko',
- 'kr',
- 'ks',
- 'ku',
- 'kv',
- 'kw',
- 'ky',
- 'la',
- 'lb',
- 'lg',
- 'li',
- 'ln',
- 'lo',
- 'lt',
- 'lu',
- 'lv',
- 'mg',
- 'mh',
- 'mi',
- 'mk',
- 'ml',
- 'mn',
- 'mr',
- 'ms',
- 'mt',
- 'my',
- 'na',
- 'nb',
- 'nd',
- 'ne',
- 'ng',
- 'nl',
- 'nn',
- 'no',
- 'nr',
- 'nv',
- 'ny',
- 'oc',
- 'oj',
- 'om',
- 'or',
- 'os',
- 'pa',
- 'pi',
- 'pl',
- 'ps',
- 'pt',
- 'qu',
- 'rm',
- 'rn',
- 'ro',
- 'ru',
- 'rw',
- 'sa',
- 'sc',
- 'sd',
- 'se',
- 'sg',
- 'si',
- 'sk',
- 'sl',
- 'sm',
- 'sn',
- 'so',
- 'sq',
- 'sr',
- 'ss',
- 'st',
- 'su',
- 'sv',
- 'sw',
- 'ta',
- 'te',
- 'tg',
- 'th',
- 'ti',
- 'tk',
- 'tl',
- 'tn',
- 'to',
- 'tr',
- 'ts',
- 'tt',
- 'tw',
- 'ty',
- 'ug',
- 'uk',
- 'ur',
- 'uz',
- 've',
- 'vi',
- 'vo',
- 'wa',
- 'wo',
- 'xh',
- 'yi',
- 'yo',
- 'za',
- 'zh',
- 'zu',
-]);
+const tags = require('language-tags');
const DEFAULT_CONFIG = {
validateValues: true,
@@ -193,9 +8,7 @@ function isValidLangTag(value) {
if (!value || !value.trim()) {
return false;
}
- const parts = value.trim().toLowerCase().split('-');
-
- return COMMON_LANG_CODES.has(parts[0]);
+ return tags(value.trim()).valid();
}
function parseConfig(config) {
diff --git a/package.json b/package.json
index de20f9800e..719f475e7f 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
"eslint-utils": "^3.0.0",
"estraverse": "^5.3.0",
"html-tags": "^3.3.1",
+ "language-tags": "^1.0.9",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"requireindex": "^1.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea58f19332..17861ed207 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -35,6 +35,9 @@ importers:
html-tags:
specifier: ^3.3.1
version: 3.3.1
+ language-tags:
+ specifier: ^1.0.9
+ version: 1.0.9
lodash.camelcase:
specifier: ^4.3.0
version: 4.3.0
@@ -2737,6 +2740,13 @@ packages:
resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==}
engines: {node: '>=18'}
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
latest-version@9.0.0:
resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==}
engines: {node: '>=18'}
@@ -6812,6 +6822,12 @@ snapshots:
ky@1.14.3: {}
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
latest-version@9.0.0:
dependencies:
package-json: 10.0.1
diff --git a/tests/lib/rules/template-require-lang-attribute.js b/tests/lib/rules/template-require-lang-attribute.js
index d7d7685d71..55956986a5 100644
--- a/tests/lib/rules/template-require-lang-attribute.js
+++ b/tests/lib/rules/template-require-lang-attribute.js
@@ -13,6 +13,8 @@ ruleTester.run('template-require-lang-attribute', rule, {
'',
'',
'',
+ '',
+ '',
'',
{
code: '',
@@ -66,6 +68,14 @@ ruleTester.run('template-require-lang-attribute', rule, {
options: [{ validateValues: true }],
errors: [{ message: ERROR_MESSAGE }],
},
+ {
+ // Invalid region subtag: "xx" is not a registered ISO 3166 / BCP 47
+ // region code. Prior to the country-codes port, the rule only
+ // validated the primary subtag and incorrectly accepted this value.
+ code: '',
+ output: null,
+ errors: [{ message: ERROR_MESSAGE }],
+ },
{
code: '',
output: null,
@@ -94,6 +104,8 @@ hbsRuleTester.run('template-require-lang-attribute', rule, {
'',
'',
'',
+ '',
+ '',
'',
{
code: '',
@@ -146,6 +158,14 @@ hbsRuleTester.run('template-require-lang-attribute', rule, {
options: [{ validateValues: true }],
errors: [{ message: ERROR_MESSAGE }],
},
+ {
+ // Invalid region subtag: "xx" is not a registered ISO 3166 / BCP 47
+ // region code. Prior to the country-codes port, the rule only
+ // validated the primary subtag and incorrectly accepted this value.
+ code: '',
+ output: null,
+ errors: [{ message: ERROR_MESSAGE }],
+ },
{
code: '',
output: null,