From 276d920ccaf560200068107f6a0370bc92b0d751 Mon Sep 17 00:00:00 2001 From: Thierry Goettelmann Date: Thu, 20 Jun 2024 09:35:36 +0200 Subject: [PATCH] feat(web-stack): add new i18n guidelines + ESLint + update and fixes (#7753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new i18n guidelines - Remove nested keys: `"foo": { "bar": "..." }` ➡️ `"foo.bar": "..."` This makes code cleaner (and as a side-effect, it now allows to use `foo` + `foo.bar` keys) - Lite/6 shared translations are no longer prefixed with `core.` - Duplicated keys across translation files are no longer accepted. This will prevent accidental overwrite of missing common translations - `fr.json` and `en.json` now required to contains the same keys - Raw text is no longer accepted in template to prevent forgetting a translation Raw text is still accepted in dev-related files (stories, dev pages...) - Remove useless `alt` on `img` - Display of percentage now uses `$n(value, 'percent')` - Removed legacy `lite/src/i18n.ts` - Add missing translations - Added visual indicator for missing translations while in dev env: `🌎❗⟨i-am-missing⟩` --- .eslintrc.js | 53 ++++++ .../lite/src/components/AccountButton.vue | 2 +- .../src/components/CollectionFilterRow.vue | 2 +- .../lite/src/components/NoDataError.vue | 2 +- .../src/components/PageUnderConstruction.vue | 2 +- .../lite/src/components/ProgressCircle.vue | 4 +- .../lite/src/components/UsageBar.vue | 2 +- .../lite/src/components/form/FormByteSize.vue | 6 +- .../src/components/infra/InfraHostItem.vue | 2 +- .../PoolDashboardCpuProvisioning.vue | 2 +- .../pool/dashboard/alarm/AlarmRow.vue | 5 +- .../dashboard/cpuUsage/PoolCpuUsageChart.vue | 4 +- .../src/components/ui/SizeStatsSummary.vue | 8 +- .../components/ui/progress/UiProgressBar.vue | 4 +- .../ui/progress/UiProgressScale.vue | 2 +- @xen-orchestra/lite/src/i18n.ts | 158 ----------------- @xen-orchestra/lite/src/locales/de.json | 147 +++++++--------- @xen-orchestra/lite/src/locales/en.json | 146 +++++++--------- @xen-orchestra/lite/src/locales/fr.json | 146 +++++++--------- @xen-orchestra/lite/src/views/HomeView.vue | 2 +- .../lite/src/views/ObjectNotFoundView.vue | 2 +- .../lite/src/views/PageNotFoundView.vue | 2 +- .../lite/src/views/pool/PoolVmsView.vue | 2 +- .../lite/src/views/settings/SettingsView.vue | 18 +- .../src/views/xoa-deploy/XoaDeployView.vue | 4 +- .../web-core/docs/guidelines/i18n.md | 162 ++++++++++++++++++ @xen-orchestra/web-core/env.d.ts | 1 + .../web-core/lib/components/CardNumbers.vue | 8 +- .../lib/components/dropdown/DropdownTitle.vue | 8 +- .../stacked-bar/StackedBarSegment.vue | 4 +- .../lib/components/table/ColumnTitle.vue | 2 +- @xen-orchestra/web-core/lib/i18n.ts | 32 ++++ @xen-orchestra/web-core/lib/locales/de.json | 34 +++- @xen-orchestra/web-core/lib/locales/en.json | 66 ++++--- @xen-orchestra/web-core/lib/locales/fr.json | 66 ++++--- .../components/account-menu/AccountMenu.vue | 2 +- .../src/components/infra/InfraHostItem.vue | 2 +- .../web/src/components/vm/VmHeader.vue | 16 +- @xen-orchestra/web/src/locales/de.json | 4 + @xen-orchestra/web/src/locales/en.json | 9 +- @xen-orchestra/web/src/locales/fr.json | 7 +- @xen-orchestra/web/src/pages/host/[id].vue | 2 +- @xen-orchestra/web/src/pages/index.vue | 6 +- @xen-orchestra/web/src/pages/pool/[id].vue | 2 +- .../web/src/pages/vm/[id]/index.vue | 2 +- package.json | 1 + yarn.lock | 92 +++++++++- 47 files changed, 737 insertions(+), 518 deletions(-) delete mode 100644 @xen-orchestra/lite/src/i18n.ts create mode 100644 @xen-orchestra/web-core/docs/guidelines/i18n.md create mode 100644 @xen-orchestra/web/src/locales/de.json diff --git a/.eslintrc.js b/.eslintrc.js index 1f6bedac15..d37026b396 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -177,6 +177,59 @@ module.exports = { 'vue/valid-define-options': 'error', }, }, + // Specific rules for XO i18n-related files + { + files: [ + '@xen-orchestra/lite/**/*.{vue,ts,json}', + '@xen-orchestra/web/**/*.{vue,ts,json}', + '@xen-orchestra/web-core/**/*.{vue,ts,json}', + ], + extends: ['plugin:@intlify/vue-i18n/base'], + settings: { + 'vue-i18n': { + localeDir: [ + '@xen-orchestra/lite/src/locales/*.json', + '@xen-orchestra/web/src/locales/*.json', + '@xen-orchestra/web-core/lib/locales/*.json', + ], + messageSyntaxVersion: '^9.9.0', + }, + }, + rules: { + strict: 'off', + 'no-irregular-whitespace': 'off', + '@intlify/vue-i18n/no-deprecated-i18n-component': 'error', + '@intlify/vue-i18n/no-deprecated-i18n-place-attr': 'error', + '@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'error', + '@intlify/vue-i18n/no-deprecated-modulo-syntax': 'error', + '@intlify/vue-i18n/no-deprecated-tc': 'error', + '@intlify/vue-i18n/no-html-messages': 'error', + '@intlify/vue-i18n/no-i18n-t-path-prop': 'error', + '@intlify/vue-i18n/no-missing-keys': 'error', + '@intlify/vue-i18n/no-raw-text': [ + 'error', + { + ignoreText: ['Xen Orchestra Lite', 'Xen Orchestra', 'XO Lite', 'XCP-ng', 'XO 5', '404', '⚠️'], + }, + ], + '@intlify/vue-i18n/no-v-html': 'error', + '@intlify/vue-i18n/valid-message-syntax': 'error', + '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error', + '@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de'] }], + }, + }, + // Specific rules for XO dev-related files + { + files: [ + '@xen-orchestra/lite/src/stories/**/*', + '@xen-orchestra/lite/src/components/component-story/**/*', + '@xen-orchestra/web/src/pages/dev/**/*', + '@xen-orchestra/lite/src/views/story/**/*', + ], + rules: { + '@intlify/vue-i18n/no-raw-text': 'off', + }, + }, { files: ['@xen-orchestra/{web-core,lite,web}/src/pages/**/*.vue'], parserOptions: { diff --git a/@xen-orchestra/lite/src/components/AccountButton.vue b/@xen-orchestra/lite/src/components/AccountButton.vue index 52911282b2..be44c9b867 100644 --- a/@xen-orchestra/lite/src/components/AccountButton.vue +++ b/@xen-orchestra/lite/src/components/AccountButton.vue @@ -11,7 +11,7 @@ {{ $t('send-us-feedback') }} - {{ $t('core.log-out') }} + {{ $t('log-out') }} diff --git a/@xen-orchestra/lite/src/components/CollectionFilterRow.vue b/@xen-orchestra/lite/src/components/CollectionFilterRow.vue index e75fd782d0..7b4dac0af4 100644 --- a/@xen-orchestra/lite/src/components/CollectionFilterRow.vue +++ b/@xen-orchestra/lite/src/components/CollectionFilterRow.vue @@ -7,7 +7,7 @@ diff --git a/@xen-orchestra/lite/src/components/UsageBar.vue b/@xen-orchestra/lite/src/components/UsageBar.vue index d60f8ff8d3..7a47831fa2 100644 --- a/@xen-orchestra/lite/src/components/UsageBar.vue +++ b/@xen-orchestra/lite/src/components/UsageBar.vue @@ -10,7 +10,7 @@ class="progress-item" > - + diff --git a/@xen-orchestra/lite/src/components/form/FormByteSize.vue b/@xen-orchestra/lite/src/components/form/FormByteSize.vue index 15aeaf6b8f..5cce5bdd61 100644 --- a/@xen-orchestra/lite/src/components/form/FormByteSize.vue +++ b/@xen-orchestra/lite/src/components/form/FormByteSize.vue @@ -2,9 +2,9 @@ - + + + diff --git a/@xen-orchestra/lite/src/components/infra/InfraHostItem.vue b/@xen-orchestra/lite/src/components/infra/InfraHostItem.vue index ba0df8a0b7..64da11c51d 100644 --- a/@xen-orchestra/lite/src/components/infra/InfraHostItem.vue +++ b/@xen-orchestra/lite/src/components/infra/InfraHostItem.vue @@ -3,7 +3,7 @@ {{ host.name_label || '(Host)' }} diff --git a/@xen-orchestra/lite/src/components/pool/dashboard/PoolDashboardCpuProvisioning.vue b/@xen-orchestra/lite/src/components/pool/dashboard/PoolDashboardCpuProvisioning.vue index 9ac704023e..0e87b5cc95 100644 --- a/@xen-orchestra/lite/src/components/pool/dashboard/PoolDashboardCpuProvisioning.vue +++ b/@xen-orchestra/lite/src/components/pool/dashboard/PoolDashboardCpuProvisioning.vue @@ -17,7 +17,7 @@
- + diff --git a/@xen-orchestra/web/src/components/infra/InfraHostItem.vue b/@xen-orchestra/web/src/components/infra/InfraHostItem.vue index 139d1f5623..81d617ef85 100644 --- a/@xen-orchestra/web/src/components/infra/InfraHostItem.vue +++ b/@xen-orchestra/web/src/components/infra/InfraHostItem.vue @@ -3,7 +3,7 @@ {{ host.name_label }} diff --git a/@xen-orchestra/web/src/locales/de.json b/@xen-orchestra/web/src/locales/de.json new file mode 100644 index 0000000000..836368ae65 --- /dev/null +++ b/@xen-orchestra/web/src/locales/de.json @@ -0,0 +1,4 @@ +{ + "account-organization-more": "Konto, Organisation & mehr…", + "tasks.quick-view": "Schnellansicht der Aufgaben (Bald verfügbar)" +} diff --git a/@xen-orchestra/web/src/locales/en.json b/@xen-orchestra/web/src/locales/en.json index 393d90a964..12c03f90ef 100644 --- a/@xen-orchestra/web/src/locales/en.json +++ b/@xen-orchestra/web/src/locales/en.json @@ -1,9 +1,4 @@ { - "account-organization-more": "Account, organization & more...", - "documentation-name": "{name} documentation", - "power-on-for-console": "Power on your VM to access its console", - "support-name": "{name} pro support", - "tasks": { - "quick-view": "Tasks' quick view (coming soon)" - } + "account-organization-more": "Account, organization & more…", + "tasks.quick-view": "Tasks' quick view (coming soon)" } diff --git a/@xen-orchestra/web/src/locales/fr.json b/@xen-orchestra/web/src/locales/fr.json index 749e4b1cd3..d603ae4212 100644 --- a/@xen-orchestra/web/src/locales/fr.json +++ b/@xen-orchestra/web/src/locales/fr.json @@ -1,9 +1,4 @@ { "account-organization-more": "Compte, organisation et plus…", - "documentation-name": "Documentation {name}", - "power-on-for-console": "Allumez votre VM pour accéder à sa console", - "support-name": "Support pro {name}", - "tasks": { - "quick-view": "Vue rapide des tâches (bientôt disponible)" - } + "tasks.quick-view": "Vue rapide des tâches (bientôt disponible)" } diff --git a/@xen-orchestra/web/src/pages/host/[id].vue b/@xen-orchestra/web/src/pages/host/[id].vue index 25f2874a37..802d134de8 100644 --- a/@xen-orchestra/web/src/pages/host/[id].vue +++ b/@xen-orchestra/web/src/pages/host/[id].vue @@ -1,6 +1,6 @@ diff --git a/@xen-orchestra/web/src/pages/index.vue b/@xen-orchestra/web/src/pages/index.vue index c85562c2cd..9b78c870d5 100644 --- a/@xen-orchestra/web/src/pages/index.vue +++ b/@xen-orchestra/web/src/pages/index.vue @@ -1,6 +1,10 @@ diff --git a/@xen-orchestra/web/src/pages/pool/[id].vue b/@xen-orchestra/web/src/pages/pool/[id].vue index 25f2874a37..802d134de8 100644 --- a/@xen-orchestra/web/src/pages/pool/[id].vue +++ b/@xen-orchestra/web/src/pages/pool/[id].vue @@ -1,6 +1,6 @@ diff --git a/@xen-orchestra/web/src/pages/vm/[id]/index.vue b/@xen-orchestra/web/src/pages/vm/[id]/index.vue index 25f2874a37..802d134de8 100644 --- a/@xen-orchestra/web/src/pages/vm/[id]/index.vue +++ b/@xen-orchestra/web/src/pages/vm/[id]/index.vue @@ -1,6 +1,6 @@ diff --git a/package.json b/package.json index 8703ba0074..befa5b416a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@babel/register": "^7.0.0", "@commitlint/cli": "^18.2.0", "@commitlint/config-conventional": "^18.1.0", + "@intlify/eslint-plugin-vue-i18n": "^3.0.0", "@limegrass/eslint-plugin-import-alias": "^1.1.0", "@typescript-eslint/parser": "^7.12.0", "@typescript-eslint/eslint-plugin": "^7.12.0", diff --git a/yarn.lock b/yarn.lock index 494a493507..d0d0470db7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1787,6 +1787,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.14.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.24.0", "@babel/template@^7.3.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -2168,6 +2175,21 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + "@eslint/js@8.57.0": version "8.57.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" @@ -2268,7 +2290,7 @@ source-map-js "^1.0.1" yaml-eslint-parser "^1.2.2" -"@intlify/core-base@9.13.1": +"@intlify/core-base@9.13.1", "@intlify/core-base@^9.12.0": version "9.13.1" resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.13.1.tgz#bd1f38e665095993ef9b67aeeb794f3cabcb515d" integrity sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w== @@ -2276,7 +2298,32 @@ "@intlify/message-compiler" "9.13.1" "@intlify/shared" "9.13.1" -"@intlify/message-compiler@9.13.1", "@intlify/message-compiler@^9.4.0": +"@intlify/eslint-plugin-vue-i18n@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@intlify/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-3.0.0.tgz#3714d15038b805e3755ef2180e0129ce09cb64ad" + integrity sha512-s4fe+VOiqMZGhDrXWnL1xLyHbcFWBcEBeD/KpVrkOtL+utH2LPTi7uZ8RvWSthMS0mUL/7L74hFJ//OUU7AYww== + dependencies: + "@eslint/eslintrc" "^3.0.0" + "@intlify/core-base" "^9.12.0" + "@intlify/message-compiler" "^9.12.0" + debug "^4.3.4" + eslint-compat-utils "^0.5.0" + glob "^10.3.3" + globals "^15.0.0" + ignore "^5.2.4" + import-fresh "^3.3.0" + is-language-code "^3.1.0" + js-yaml "^4.1.0" + json5 "^2.2.3" + jsonc-eslint-parser "^2.3.0" + lodash "^4.17.21" + parse5 "^7.1.2" + semver "^7.5.4" + synckit "^0.9.0" + vue-eslint-parser "^9.3.1" + yaml-eslint-parser "^1.2.2" + +"@intlify/message-compiler@9.13.1", "@intlify/message-compiler@^9.12.0", "@intlify/message-compiler@^9.4.0": version "9.13.1" resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.13.1.tgz#ff8129badf77db3fb648b8d3cceee87c8033ed0a" integrity sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w== @@ -10469,6 +10516,11 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + eslint@^8.7.0: version "8.57.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" @@ -10528,6 +10580,15 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" +espree@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" + integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww== + dependencies: + acorn "^8.11.3" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + espree@^9.0.0, espree@^9.3.1, espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" @@ -11920,6 +11981,16 @@ globals@^13.19.0, globals@^13.24.0: dependencies: type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.0.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.4.0.tgz#3e36ea6e4d9ddcf1cb42d92f5c4a145a8a2ddc1c" + integrity sha512-unnwvMZpv0eDUyjNyh9DH/yxUaRYrEjW/qK4QcdrHg3oO11igUQrCSgODHEqxlKg8v2CD2Sd7UkqqEBoz5U7TQ== + globalthis@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -13557,6 +13628,13 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-language-code@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-language-code/-/is-language-code-3.1.0.tgz#b2386b49227e7010636f16d0c2c681ca40136ab5" + integrity sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA== + dependencies: + "@babel/runtime" "^7.14.0" + is-lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a" @@ -17327,7 +17405,7 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" -parse5@^7.0.0: +parse5@^7.0.0, parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== @@ -21321,6 +21399,14 @@ synckit@^0.8.6: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +synckit@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.0.tgz#5b33b458b3775e4466a5b377fba69c63572ae449" + integrity sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + syntax-error@^1.1.1: version "1.4.0" resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c"