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,