scratch-l10n 5.0.308 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +6 -0
  3. package/dist/l10n.js +3 -0
  4. package/dist/l10n.js.map +1 -1
  5. package/dist/localeData.js +3 -0
  6. package/dist/localeData.js.map +1 -1
  7. package/dist/supportedLocales.js +3 -0
  8. package/dist/supportedLocales.js.map +1 -1
  9. package/package.json +24 -22
  10. package/scripts/{build-data.mjs → build-data.mts} +15 -6
  11. package/scripts/{build-i18n-src.js → build-i18n-src.mts} +16 -11
  12. package/scripts/lib/concurrent.mts +37 -0
  13. package/scripts/lib/freshdesk-api.mts +322 -0
  14. package/scripts/lib/help-utils.mts +221 -0
  15. package/{lib/progress-logger.mjs → scripts/lib/progress-logger.mts} +10 -5
  16. package/scripts/lib/transifex-formats.mts +53 -0
  17. package/scripts/lib/transifex-objects.mts +143 -0
  18. package/scripts/lib/transifex.mts +284 -0
  19. package/scripts/lib/validate.mts +107 -0
  20. package/scripts/tsconfig.json +20 -0
  21. package/scripts/tx-pull-editor.mts +74 -0
  22. package/scripts/{tx-pull-help-articles.js → tx-pull-help-articles.mts} +5 -13
  23. package/scripts/{tx-pull-help-names.js → tx-pull-help-names.mts} +5 -13
  24. package/scripts/{tx-pull-locale-articles.js → tx-pull-locale-articles.mts} +5 -13
  25. package/scripts/{tx-pull-www.mjs → tx-pull-www.mts} +16 -29
  26. package/scripts/{tx-push-help.mjs → tx-push-help.mts} +39 -37
  27. package/scripts/{tx-push-src.js → tx-push-src.mts} +13 -20
  28. package/scripts/update-translations.sh +2 -2
  29. package/scripts/{validate-extension-inputs.mjs → validate-extension-inputs.mts} +20 -10
  30. package/scripts/{validate-translations.mjs → validate-translations.mts} +7 -12
  31. package/scripts/{validate-www.mjs → validate-www.mts} +15 -13
  32. package/src/supported-locales.mjs +3 -0
  33. package/www/scratch-website.about-l10njson/nn.json +1 -1
  34. package/www/scratch-website.splash-l10njson/mi.json +2 -2
  35. package/www/scratch-website.teacher-faq-l10njson/cy.json +1 -1
  36. package/.github/PULL_REQUEST_TEMPLATE.md +0 -75
  37. package/.github/workflows/ci-cd.yml +0 -55
  38. package/.github/workflows/commitlint.yml +0 -12
  39. package/.github/workflows/daily-help-update.yml +0 -40
  40. package/.github/workflows/daily-tx-pull.yml +0 -54
  41. package/.github/workflows/signature-assistant.yml +0 -31
  42. package/lib/batch.js +0 -15
  43. package/lib/transifex.js +0 -242
  44. package/lib/validate.mjs +0 -48
  45. package/scripts/freshdesk-api.js +0 -149
  46. package/scripts/help-utils.js +0 -190
  47. package/scripts/tx-pull-editor.mjs +0 -83
@@ -222,6 +222,9 @@ const customLocales = {
222
222
  },
223
223
  }
224
224
 
225
+ /**
226
+ * @type {Record<string,string>}
227
+ */
225
228
  const localeMap = {
226
229
  'aa-dj': 'aa_DJ',
227
230
  'es-419': 'es_419',
@@ -1 +1 @@
1
- {"version":3,"file":"supportedLocales.js","sources":["webpack:///webpack/bootstrap","webpack:///./src/supported-locales.mjs"],"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./src/supported-locales.mjs\");\n","/**\n * Currently supported locales for the Scratch Project\n * @type {object} Key Value pairs of locale code: Language name written in the language\n */\n\nconst locales = {\n ab: { name: 'Аҧсшәа' },\n af: { name: 'Afrikaans' },\n ar: { name: 'العربية' },\n am: { name: 'አማርኛ' },\n an: { name: 'Aragonés' },\n ast: { name: 'Asturianu' },\n az: { name: 'Azeri' },\n id: { name: 'Bahasa Indonesia' },\n bn: { name: 'বাংলা' },\n be: { name: 'Беларуская' },\n bg: { name: 'Български' },\n ca: { name: 'Català' },\n cs: { name: 'Česky' },\n cy: { name: 'Cymraeg' },\n da: { name: 'Dansk' },\n de: { name: 'Deutsch' },\n et: { name: 'Eesti' },\n el: { name: 'Ελληνικά' },\n en: { name: 'English' },\n es: { name: 'Español (España)' },\n 'es-419': { name: 'Español Latinoamericano' },\n eo: { name: 'Esperanto' },\n eu: { name: 'Euskara' },\n fa: { name: 'فارسی' },\n fil: { name: 'Filipino' },\n fr: { name: 'Français' },\n fy: { name: 'Frysk' },\n ga: { name: 'Gaeilge' },\n gd: { name: 'Gàidhlig' },\n gl: { name: 'Galego' },\n ko: { name: '한국어' },\n ha: { name: 'Hausa' },\n hy: { name: 'Հայերեն' },\n he: { name: 'עִבְרִית' },\n hi: { name: 'हिंदी' },\n hr: { name: 'Hrvatski' },\n xh: { name: 'isiXhosa' },\n zu: { name: 'isiZulu' },\n is: { name: 'Íslenska' },\n it: { name: 'Italiano' },\n ka: { name: 'ქართული ენა' },\n kk: { name: 'қазақша' },\n qu: { name: 'Kichwa' },\n sw: { name: 'Kiswahili' },\n ht: { name: 'Kreyòl ayisyen' },\n ku: { name: 'Kurdî' },\n ckb: { name: 'کوردیی ناوەندی' },\n lv: { name: 'Latviešu' },\n lt: { name: 'Lietuvių' },\n hu: { name: 'Magyar' },\n mi: { name: 'Māori' },\n mn: { name: 'Монгол хэл' },\n nl: { name: 'Nederlands' },\n ja: { name: '日本語' },\n 'ja-Hira': { name: 'にほんご' },\n nb: { name: 'Norsk Bokmål' },\n nn: { name: 'Norsk Nynorsk' },\n oc: { name: 'Occitan' },\n or: { name: 'ଓଡ଼ିଆ' },\n uz: { name: 'Oʻzbekcha' },\n th: { name: 'ไทย' },\n km: { name: 'ភាសាខ្មែរ' },\n pl: { name: 'Polski' },\n pt: { name: 'Português' },\n 'pt-br': { name: 'Português Brasileiro' },\n rap: { name: 'Rapa Nui' },\n ro: { name: 'Română' },\n ru: { name: 'Русский' },\n nso: { name: 'Sepedi' },\n tn: { name: 'Setswana' },\n sk: { name: 'Slovenčina' },\n sl: { name: 'Slovenščina' },\n sr: { name: 'Српски' },\n fi: { name: 'Suomi' },\n sv: { name: 'Svenska' },\n vi: { name: 'Tiếng Việt' },\n tr: { name: 'Türkçe' },\n uk: { name: 'Українська' },\n 'zh-cn': { name: '简体中文' },\n 'zh-tw': { name: '繁體中文' },\n}\n\nconst customLocales = {\n ab: {\n locale: 'ab',\n parentLocale: 'ru',\n },\n // Aragonese is not in the locale data, using es for Spain\n an: {\n locale: 'an',\n parentLocale: 'es',\n },\n // haitian creole is not in locale-langData\n ht: {\n locale: 'ht',\n parentLocale: 'fr',\n },\n oc: {\n locale: 'oc',\n parentLocale: 'fr',\n },\n rap: {\n locale: 'rap',\n parentLocale: 'es',\n },\n // TODO: replace zh-cn, zh-tw with zh-Hans and zh-Hant then customLocales is unnecessary\n 'zh-cn': {\n locale: 'zh-cn',\n parentLocale: 'zh',\n },\n 'zh-tw': {\n locale: 'zh-tw',\n parentLocale: 'zh',\n },\n}\n\nconst localeMap = {\n 'aa-dj': 'aa_DJ',\n 'es-419': 'es_419',\n // ja-Hira: no map - it's 'ja-Hira' on transifex\n 'pt-br': 'pt_BR',\n 'zh-cn': 'zh_CN',\n 'zh-tw': 'zh_TW',\n}\n\n// list of RTL locales supported, and a function to check whether a locale is RTL\nconst rtlLocales = ['ar', 'ckb', 'fa', 'he']\n\nconst isRtl = locale => rtlLocales.indexOf(locale) !== -1\n\nexport { locales as default, customLocales, localeMap, isRtl }\n"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;AClFA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;A","sourceRoot":""}
1
+ {"version":3,"file":"supportedLocales.js","sources":["webpack:///webpack/bootstrap","webpack:///./src/supported-locales.mjs"],"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./src/supported-locales.mjs\");\n","/**\n * Currently supported locales for the Scratch Project\n * @type {object} Key Value pairs of locale code: Language name written in the language\n */\n\nconst locales = {\n ab: { name: 'Аҧсшәа' },\n af: { name: 'Afrikaans' },\n ar: { name: 'العربية' },\n am: { name: 'አማርኛ' },\n an: { name: 'Aragonés' },\n ast: { name: 'Asturianu' },\n az: { name: 'Azeri' },\n id: { name: 'Bahasa Indonesia' },\n bn: { name: 'বাংলা' },\n be: { name: 'Беларуская' },\n bg: { name: 'Български' },\n ca: { name: 'Català' },\n cs: { name: 'Česky' },\n cy: { name: 'Cymraeg' },\n da: { name: 'Dansk' },\n de: { name: 'Deutsch' },\n et: { name: 'Eesti' },\n el: { name: 'Ελληνικά' },\n en: { name: 'English' },\n es: { name: 'Español (España)' },\n 'es-419': { name: 'Español Latinoamericano' },\n eo: { name: 'Esperanto' },\n eu: { name: 'Euskara' },\n fa: { name: 'فارسی' },\n fil: { name: 'Filipino' },\n fr: { name: 'Français' },\n fy: { name: 'Frysk' },\n ga: { name: 'Gaeilge' },\n gd: { name: 'Gàidhlig' },\n gl: { name: 'Galego' },\n ko: { name: '한국어' },\n ha: { name: 'Hausa' },\n hy: { name: 'Հայերեն' },\n he: { name: 'עִבְרִית' },\n hi: { name: 'हिंदी' },\n hr: { name: 'Hrvatski' },\n xh: { name: 'isiXhosa' },\n zu: { name: 'isiZulu' },\n is: { name: 'Íslenska' },\n it: { name: 'Italiano' },\n ka: { name: 'ქართული ენა' },\n kk: { name: 'қазақша' },\n qu: { name: 'Kichwa' },\n sw: { name: 'Kiswahili' },\n ht: { name: 'Kreyòl ayisyen' },\n ku: { name: 'Kurdî' },\n ckb: { name: 'کوردیی ناوەندی' },\n lv: { name: 'Latviešu' },\n lt: { name: 'Lietuvių' },\n hu: { name: 'Magyar' },\n mi: { name: 'Māori' },\n mn: { name: 'Монгол хэл' },\n nl: { name: 'Nederlands' },\n ja: { name: '日本語' },\n 'ja-Hira': { name: 'にほんご' },\n nb: { name: 'Norsk Bokmål' },\n nn: { name: 'Norsk Nynorsk' },\n oc: { name: 'Occitan' },\n or: { name: 'ଓଡ଼ିଆ' },\n uz: { name: 'Oʻzbekcha' },\n th: { name: 'ไทย' },\n km: { name: 'ភាសាខ្មែរ' },\n pl: { name: 'Polski' },\n pt: { name: 'Português' },\n 'pt-br': { name: 'Português Brasileiro' },\n rap: { name: 'Rapa Nui' },\n ro: { name: 'Română' },\n ru: { name: 'Русский' },\n nso: { name: 'Sepedi' },\n tn: { name: 'Setswana' },\n sk: { name: 'Slovenčina' },\n sl: { name: 'Slovenščina' },\n sr: { name: 'Српски' },\n fi: { name: 'Suomi' },\n sv: { name: 'Svenska' },\n vi: { name: 'Tiếng Việt' },\n tr: { name: 'Türkçe' },\n uk: { name: 'Українська' },\n 'zh-cn': { name: '简体中文' },\n 'zh-tw': { name: '繁體中文' },\n}\n\nconst customLocales = {\n ab: {\n locale: 'ab',\n parentLocale: 'ru',\n },\n // Aragonese is not in the locale data, using es for Spain\n an: {\n locale: 'an',\n parentLocale: 'es',\n },\n // haitian creole is not in locale-langData\n ht: {\n locale: 'ht',\n parentLocale: 'fr',\n },\n oc: {\n locale: 'oc',\n parentLocale: 'fr',\n },\n rap: {\n locale: 'rap',\n parentLocale: 'es',\n },\n // TODO: replace zh-cn, zh-tw with zh-Hans and zh-Hant then customLocales is unnecessary\n 'zh-cn': {\n locale: 'zh-cn',\n parentLocale: 'zh',\n },\n 'zh-tw': {\n locale: 'zh-tw',\n parentLocale: 'zh',\n },\n}\n\n/**\n * @type {Record<string,string>}\n */\nconst localeMap = {\n 'aa-dj': 'aa_DJ',\n 'es-419': 'es_419',\n // ja-Hira: no map - it's 'ja-Hira' on transifex\n 'pt-br': 'pt_BR',\n 'zh-cn': 'zh_CN',\n 'zh-tw': 'zh_TW',\n}\n\n// list of RTL locales supported, and a function to check whether a locale is RTL\nconst rtlLocales = ['ar', 'ckb', 'fa', 'he']\n\nconst isRtl = locale => rtlLocales.indexOf(locale) !== -1\n\nexport { locales as default, customLocales, localeMap, isRtl }\n"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;AClFA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;A","sourceRoot":""}
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
1
  {
2
2
  "name": "scratch-l10n",
3
- "version": "5.0.308",
3
+ "version": "6.0.0",
4
4
  "description": "Localization for the Scratch 3.0 components",
5
5
  "main": "./dist/l10n.js",
6
6
  "browser": "./src/index.mjs",
7
7
  "bin": {
8
- "build-i18n-src": "./scripts/build-i18n-src.js",
9
- "tx-push-src": "./scripts/tx-push-src.js"
8
+ "build-i18n-src": "./scripts/build-i18n-src.mts",
9
+ "tx-push-src": "./scripts/tx-push-src.mts"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "npm run clean && npm run build:data && webpack --progress --colors --bail",
13
- "build:data": "node scripts/build-data.mjs",
13
+ "build:data": "./scripts/build-data.mts",
14
14
  "clean": "rimraf ./dist ./locales && mkdirp dist locales",
15
15
  "format": "prettier --write . && eslint --fix",
16
16
  "lint": "npm run lint:js && npm run lint:json",
17
17
  "lint:js": "eslint && prettier --check .",
18
18
  "lint:json": "jshint -e .json www editor/blocks editor/extensions editor/interface editor/paint-editor",
19
19
  "prepare": "husky install",
20
- "pull:blocks": "node scripts/tx-pull-editor.mjs scratch-editor blocks ./editor/blocks/",
20
+ "pull:blocks": "./scripts/tx-pull-editor.mts scratch-editor blocks ./editor/blocks/",
21
21
  "pull:editor": "npm run pull:blocks && npm run pull:extensions && npm run pull:paint && npm run pull:interface",
22
- "pull:extensions": "node scripts/tx-pull-editor.mjs scratch-editor extensions ./editor/extensions/",
22
+ "pull:extensions": "./scripts/tx-pull-editor.mts scratch-editor extensions ./editor/extensions/",
23
23
  "pull:help": "npm run pull:help:names && npm run pull:help:articles",
24
- "pull:help:articles": "./scripts/tx-pull-help-articles.js",
25
- "pull:help:names": "./scripts/tx-pull-help-names.js",
26
- "pull:interface": "node scripts/tx-pull-editor.mjs scratch-editor interface ./editor/interface/",
27
- "pull:paint": "node scripts/tx-pull-editor.mjs scratch-editor paint-editor ./editor/paint-editor/",
28
- "pull:www": "node scripts/tx-pull-www.mjs ./www",
29
- "push:help": "./scripts/tx-push-help.mjs",
24
+ "pull:help:articles": "./scripts/tx-pull-help-articles.mts",
25
+ "pull:help:names": "./scripts/tx-pull-help-names.mts",
26
+ "pull:interface": "./scripts/tx-pull-editor.mts scratch-editor interface ./editor/interface/",
27
+ "pull:paint": "./scripts/tx-pull-editor.mts scratch-editor paint-editor ./editor/paint-editor/",
28
+ "pull:www": "./scripts/tx-pull-www.mts ./www",
29
+ "push:help": "./scripts/tx-push-help.mts",
30
30
  "sync:help": "npm run push:help && npm run pull:help",
31
31
  "test": "npm run lint:js && npm run validate:editor && npm run validate:www && npm run build && npm run lint:json",
32
32
  "update": "scripts/update-translations.sh",
33
- "validate:blocks": "node scripts/validate-translations.mjs ./editor/blocks/",
33
+ "validate:blocks": "./scripts/validate-translations.mts ./editor/blocks/",
34
34
  "validate:editor": "npm run validate:blocks && npm run validate:extensions && npm run validate:interface && npm run validate:paint",
35
- "validate:extensions": "node scripts/validate-translations.mjs ./editor/extensions/ && node scripts/validate-extension-inputs.mjs",
36
- "validate:interface": "node scripts/validate-translations.mjs ./editor/interface/",
37
- "validate:paint": "node scripts/validate-translations.mjs ./editor/paint-editor/",
38
- "validate:www": "node scripts/validate-www.mjs ./www"
35
+ "validate:extensions": "./scripts/validate-translations.mts ./editor/extensions/ && ./scripts/validate-extension-inputs.mts",
36
+ "validate:interface": "./scripts/validate-translations.mts ./editor/interface/",
37
+ "validate:paint": "./scripts/validate-translations.mts ./editor/paint-editor/",
38
+ "validate:www": "./scripts/validate-www.mts ./www"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
@@ -49,8 +49,8 @@
49
49
  "homepage": "https://github.com/scratchfoundation/scratch-l10n#readme",
50
50
  "dependencies": {
51
51
  "@transifex/api": "7.1.4",
52
- "download": "8.0.0",
53
- "transifex": "1.6.6"
52
+ "transifex": "1.6.6",
53
+ "tsx": "4.19.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@babel/cli": "7.28.0",
@@ -63,11 +63,14 @@
63
63
  "@babel/preset-react": "7.27.1",
64
64
  "@commitlint/cli": "18.6.1",
65
65
  "@commitlint/config-conventional": "18.6.3",
66
+ "@types/async": "3.2.24",
67
+ "@types/glob": "8.1.0",
68
+ "@types/lodash.defaultsdeep": "4.6.9",
66
69
  "async": "3.2.6",
67
70
  "babel-loader": "8.4.1",
68
71
  "babel-plugin-react-intl": "3.5.1",
69
72
  "eslint": "9.31.0",
70
- "eslint-config-scratch": "11.0.35",
73
+ "eslint-config-scratch": "11.0.36",
71
74
  "format-message-cli": "6.2.4",
72
75
  "format-message-parse": "6.2.4",
73
76
  "glob": "7.2.3",
@@ -77,8 +80,7 @@
77
80
  "json": "^9.0.6",
78
81
  "jsonlint": "1.6.3",
79
82
  "lodash.defaultsdeep": "4.6.1",
80
- "mkdirp": "0.5.1",
81
- "node-fetch": "2.7.0",
83
+ "mkdirp": "3.0.1",
82
84
  "p-limit": "2.3.0",
83
85
  "p-queue": "3.2.0",
84
86
  "prettier": "3.5.3",
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env tsx
2
2
  /*
3
3
  Generates locales/<component>-msgs.js for each component (gui, etc) from the
4
4
  current translation files for each language for that component
@@ -39,18 +39,27 @@ Missing locales are ignored, react-intl will use the default messages for them.
39
39
  */
40
40
  import * as fs from 'fs'
41
41
  import defaultsDeep from 'lodash.defaultsdeep'
42
- import mkdirp from 'mkdirp'
42
+ import { mkdirp } from 'mkdirp'
43
43
  import * as path from 'path'
44
44
  import locales from '../src/supported-locales.mjs'
45
+ import { TransifexEditorStrings } from './lib/validate.mts'
45
46
 
46
47
  const MSGS_DIR = './locales/'
47
48
  mkdirp.sync(MSGS_DIR)
48
- const missingLocales = []
49
49
 
50
- const combineJson = component =>
51
- Object.keys(locales).reduce((collection, lang) => {
50
+ const missingLocales: string[] = []
51
+
52
+ /**
53
+ * For a given component, load translation data from multiple locales and merge all of that into one translation map.
54
+ * @param component - the name of an editor component
55
+ * @returns translation data for the requested component
56
+ */
57
+ const combineJson = (component: string) =>
58
+ Object.keys(locales).reduce((collection: Record<string, TransifexEditorStrings>, lang) => {
52
59
  try {
53
- const langData = JSON.parse(fs.readFileSync(path.resolve('editor', component, lang + '.json'), 'utf8'))
60
+ const langData = JSON.parse(
61
+ fs.readFileSync(path.resolve('editor', component, lang + '.json'), 'utf8'),
62
+ ) as TransifexEditorStrings
54
63
  collection[lang] = langData
55
64
  } catch {
56
65
  missingLocales.push(component + ':' + lang + '\n')
@@ -1,11 +1,11 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env tsx
2
+ import fs from 'fs'
3
+ import glob from 'glob'
4
+ import { mkdirp } from 'mkdirp'
5
+ import path from 'path'
6
+ import { TransifexStringChrome, TransifexStrings } from './lib/transifex-formats.mts'
2
7
 
3
- const fs = require('fs')
4
- const glob = require('glob')
5
- const path = require('path')
6
- const mkdirp = require('mkdirp')
7
-
8
- var args = process.argv.slice(2)
8
+ const args = process.argv.slice(2)
9
9
 
10
10
  if (!args.length) {
11
11
  process.stdout.write('You must specify the messages dir generated by babel-plugin-react-intl.\n')
@@ -14,12 +14,17 @@ if (!args.length) {
14
14
 
15
15
  const MESSAGES_PATTERN = args.shift() + '/**/*.json'
16
16
 
17
- if (!args.length) {
17
+ const LANG_DIR = args.shift()
18
+ if (!LANG_DIR) {
18
19
  process.stdout.write('A destination directory must be specified.\n')
19
20
  process.exit(1)
20
21
  }
21
22
 
22
- const LANG_DIR = args.shift()
23
+ interface Descriptor {
24
+ id: string
25
+ description: string
26
+ defaultMessage: string
27
+ }
23
28
 
24
29
  // Aggregates the default messages that were extracted from the example app's
25
30
  // React components via the React Intl Babel plugin. An error will be thrown if
@@ -29,8 +34,8 @@ const LANG_DIR = args.shift()
29
34
  const defaultMessages = glob
30
35
  .sync(MESSAGES_PATTERN)
31
36
  .map(filename => fs.readFileSync(filename, 'utf8'))
32
- .map(file => JSON.parse(file))
33
- .reduce((collection, descriptors) => {
37
+ .map(file => JSON.parse(file) as Descriptor[])
38
+ .reduce((collection: TransifexStrings<TransifexStringChrome>, descriptors) => {
34
39
  descriptors.forEach(({ id, defaultMessage, description }) => {
35
40
  if (Object.prototype.hasOwnProperty.call(collection, id)) {
36
41
  throw new Error(`Duplicate message id: ${id}`)
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @param item - An item to process
3
+ * @returns The result of processing the item
4
+ */
5
+ type ProcessItemAsync<T, R> = (item: T) => Promise<R>
6
+
7
+ /**
8
+ * Maps each value of an array into an async function, then returns an array of the results.
9
+ * Up to `poolSize` calls to the async function are allowed to run concurrently.
10
+ * @param arr - Array of input values.
11
+ * @param poolSize - Number of calls to `func` allowed to run concurrently.
12
+ * If `poolSize` is less than 2, tasks will be run sequentially.
13
+ * @param func - Function to apply to each item in `arr`.
14
+ * @returns Results of `func` applied to each item in `arr`.
15
+ */
16
+ export const poolMap = async <T, R>(arr: T[], poolSize: number, func: ProcessItemAsync<T, R>): Promise<R[]> => {
17
+ const pool: Record<number, Promise<void>> = []
18
+ const results: R[] = []
19
+
20
+ const processItem = async (i: number): Promise<void> => {
21
+ results[i] = await func(arr[i])
22
+ delete pool[i]
23
+ }
24
+
25
+ for (let i = 0; i < arr.length; i++) {
26
+ pool[i] = processItem(i) // No `await` here: we're storing the promise, not the result
27
+ if (Object.keys(pool).length >= poolSize) {
28
+ // Wait for one of the promises in the pool to resolve
29
+ // and clear itself to make room for the next one
30
+ await Promise.race(Object.values(pool))
31
+ }
32
+ }
33
+
34
+ // Wait for any remaining promises to resolve
35
+ await Promise.all(Object.values(pool))
36
+ return results
37
+ }
@@ -0,0 +1,322 @@
1
+ // interface to FreshDesk Solutions (knowledge base) api
2
+
3
+ /**
4
+ * Extend
5
+ */
6
+ export interface HttpError extends Error {
7
+ /** HTTP error code (if any) */
8
+ code: number | null | undefined
9
+
10
+ /** Retry-After header value (if any) */
11
+ retryAfter: string | null | undefined
12
+ }
13
+
14
+ /**
15
+ * Properties to provide when creating a Category throught the Freshdesk API. Also works for updates.
16
+ * @see https://developers.freshdesk.com/api/#create_solution_category
17
+ */
18
+ export interface FreshdeskCategoryCreate {
19
+ /** Name of the solution category. Mandatory for Create. */
20
+ name: string
21
+ /** Description of the solution category */
22
+ description?: string
23
+ /** List of portal IDs where this category is visible */
24
+ visible_in_portals?: number[]
25
+ }
26
+
27
+ /**
28
+ * Categories broadly classify your solutions page into several sections.
29
+ * @see https://developers.freshdesk.com/api/#solution_category_attributes
30
+ */
31
+ export interface FreshdeskCategory extends FreshdeskCategoryCreate {
32
+ /** Unique ID of the solution category */
33
+ id?: number
34
+ /** Solution Category creation timestamp */
35
+ created_at?: string
36
+ /** Solution Category update timestamp */
37
+ updated_at?: string
38
+ }
39
+
40
+ /**
41
+ * Properties to provide when creating a Folder throught the Freshdesk API. Also works for updates.
42
+ * @see https://developers.freshdesk.com/api/#create_solution_folder
43
+ */
44
+ export interface FreshdeskFolderCreate {
45
+ /** Description of the solution folder */
46
+ description?: string
47
+ /** Name of the solution folder */
48
+ name: string
49
+ /** ID of the parent folder */
50
+ parent_folder_id?: number
51
+ /** Accessibility of this folder. Please refer to Folder Properties table. */
52
+ visibility?: FreshdeskFolderVisibility
53
+ /** IDs of the companies to whom this solution folder is visible */
54
+ company_ids?: number[]
55
+ /** IDs of the contact segments to whom this solution folder is visible */
56
+ contact_segment_ids?: number[]
57
+ /** IDs of the company segments to whom this solution folder is visible */
58
+ company_segment_ids?: number[]
59
+ }
60
+
61
+ /**
62
+ * Related Solutions Articles and/or Folders are organized into Folders. Folders make it convenient for users to read
63
+ * similar articles or navigate to other possible solutions to their problem.
64
+ * @see https://developers.freshdesk.com/api/#solution_folder_attributes
65
+ */
66
+ export interface FreshdeskFolder extends FreshdeskFolderCreate {
67
+ /** Unique ID of the solution folder */
68
+ id?: number
69
+ /** Parent category and folders in which the folder is placed */
70
+ hierarchy?: object[]
71
+ /** Number of articles present inside a folder */
72
+ articles_count?: number
73
+ /** Number of folders present inside a folder */
74
+ sub_folders_count?: number
75
+ /** Solution Folder creation timestamp */
76
+ created_at?: string
77
+ /** Solution Folder updated timestamp */
78
+ updated_at?: string
79
+ }
80
+
81
+ export enum FreshdeskFolderVisibility {
82
+ AllUsers = 1,
83
+ LoggedInUsers = 2,
84
+ Agents = 3,
85
+ SelectedCompanies = 4,
86
+ Bots = 5,
87
+ SelectedContactSegments = 6,
88
+ SelectedCompanySegments = 7,
89
+ }
90
+
91
+ /**
92
+ * Properties to provide when creating an Article throught the Freshdesk API. Also works for updates.
93
+ * @see https://developers.freshdesk.com/api/#create_solution_article
94
+ */
95
+ export interface FreshdeskArticleCreate {
96
+ /** ID of the agent who created the solution article */
97
+ agent_id?: number
98
+ /** Description of the solution article */
99
+ description: string
100
+ /** Status of the solution article */
101
+ status: FreshdeskArticleStatus
102
+ /** Meta data for search engine optimization. Allows meta_title, meta_description and meta_keywords */
103
+ seo_data?: FreshdeskSEOData
104
+ /** Tags that have been associated with the solution article */
105
+ tags?: string[]
106
+ /** Title of the solution article */
107
+ title: string
108
+ }
109
+
110
+ /**
111
+ * Solution Articles or knowledge base posts promote self-help in your support portal. These should ideally cover all
112
+ * aspects of your product or service like "how-to" instructions and FAQs.
113
+ * @see https://developers.freshdesk.com/api/#solution_article_attributes
114
+ */
115
+ export interface FreshdeskArticle extends FreshdeskArticleCreate {
116
+ /** Unique ID of the solution article */
117
+ id?: number
118
+ /** ID of the category to which the solution article belongs */
119
+ category_id?: number
120
+ /** Description of the solution article in plain text */
121
+ description_text?: string
122
+ /** ID of the folder to which the solution article belongs */
123
+ folder_id?: number
124
+ /** Parent category and folders in which the article is placed */
125
+ hierarchy?: object[]
126
+ /** Number of views for the solution article */
127
+ hits?: number
128
+ /** Number of down votes for the solution article */
129
+ thumbs_down?: number
130
+ /** Number of upvotes for the solution article */
131
+ thumbs_up?: number
132
+ /** Solution Article creation timestamp */
133
+ created_at?: string
134
+ /** Solution Article updated timestamp */
135
+ updated_at?: string
136
+ }
137
+
138
+ export enum FreshdeskArticleStatus {
139
+ draft = 1,
140
+ published = 2,
141
+ }
142
+
143
+ export interface FreshdeskSEOData {
144
+ meta_title?: string
145
+ meta_description?: string
146
+ meta_keywords?: string[]
147
+ }
148
+
149
+ /**
150
+ * Wrapper for Freshdesk's REST API
151
+ */
152
+ export class FreshdeskApi {
153
+ baseUrl: string
154
+ private _auth: string
155
+ defaultHeaders: { 'Content-Type': string; Authorization: string }
156
+ rateLimited: boolean
157
+
158
+ constructor(baseUrl: string, apiKey: string) {
159
+ this.baseUrl = baseUrl
160
+ this._auth = 'Basic ' + Buffer.from(`${apiKey}:X`).toString('base64')
161
+ this.defaultHeaders = {
162
+ 'Content-Type': 'application/json',
163
+ Authorization: this._auth,
164
+ }
165
+ this.rateLimited = false
166
+ }
167
+
168
+ /**
169
+ * Checks the status of a response. If status is not ok, or the body is not JSON, raise exception
170
+ * @param res - The response object
171
+ * @returns the response if it is ok
172
+ */
173
+ checkStatus(res: Response) {
174
+ if (res.ok) {
175
+ if (res.headers.get('content-type')?.includes('application/json')) {
176
+ return res
177
+ }
178
+ throw new Error(`response not json: ${res.headers.get('content-type')}`)
179
+ }
180
+ const err = new Error(`response ${res.statusText}`) as HttpError
181
+ err.code = res.status
182
+ if (res.status === 429) {
183
+ err.retryAfter = res.headers.get('Retry-After')
184
+ }
185
+ throw err
186
+ }
187
+
188
+ async listCategories(): Promise<FreshdeskCategory[]> {
189
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/categories`, { headers: this.defaultHeaders })
190
+ this.checkStatus(res)
191
+ return (await res.json()) as FreshdeskCategory[]
192
+ }
193
+
194
+ async listFolders(category: FreshdeskCategory): Promise<FreshdeskFolder[]> {
195
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/categories/${category.id}/folders`, {
196
+ headers: this.defaultHeaders,
197
+ })
198
+ this.checkStatus(res)
199
+ return (await res.json()) as FreshdeskFolder[]
200
+ }
201
+
202
+ async listArticles(folder: FreshdeskFolder) {
203
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/folders/${folder.id}/articles`, {
204
+ headers: this.defaultHeaders,
205
+ })
206
+ this.checkStatus(res)
207
+ return (await res.json()) as FreshdeskArticle[]
208
+ }
209
+
210
+ async updateCategoryTranslation(
211
+ id: FreshdeskCategory['id'],
212
+ locale: string,
213
+ body: FreshdeskCategoryCreate,
214
+ ): Promise<FreshdeskCategory | -1> {
215
+ if (this.rateLimited) {
216
+ process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
217
+ return -1
218
+ }
219
+ try {
220
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/categories/${id}/${locale}`, {
221
+ method: 'put',
222
+ body: JSON.stringify(body),
223
+ headers: this.defaultHeaders,
224
+ })
225
+ this.checkStatus(res)
226
+ return (await res.json()) as FreshdeskCategory
227
+ } catch (err) {
228
+ const httpError = err as HttpError
229
+ if (httpError.code === 404) {
230
+ // not found, try create instead
231
+ const res2 = await fetch(`${this.baseUrl}/api/v2/solutions/categories/${id}/${locale}`, {
232
+ method: 'post',
233
+ body: JSON.stringify(body),
234
+ headers: this.defaultHeaders,
235
+ })
236
+ this.checkStatus(res2)
237
+ return (await res2.json()) as FreshdeskCategory
238
+ }
239
+ if (httpError.code === 429) {
240
+ this.rateLimited = true
241
+ }
242
+ process.stdout.write(`Error processing id ${id} for locale ${locale}: ${httpError.message}\n`)
243
+ throw err
244
+ }
245
+ }
246
+
247
+ async updateFolderTranslation(
248
+ id: FreshdeskFolder['id'],
249
+ locale: string,
250
+ body: FreshdeskFolderCreate,
251
+ ): Promise<FreshdeskFolder | -1> {
252
+ if (this.rateLimited) {
253
+ process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
254
+ return -1
255
+ }
256
+ try {
257
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/folders/${id}/${locale}`, {
258
+ method: 'put',
259
+ body: JSON.stringify(body),
260
+ headers: this.defaultHeaders,
261
+ })
262
+ this.checkStatus(res)
263
+ return (await res.json()) as FreshdeskFolder
264
+ } catch (err) {
265
+ const httpError = err as HttpError
266
+ if (httpError.code === 404) {
267
+ // not found, try create instead
268
+ const res2 = await fetch(`${this.baseUrl}/api/v2/solutions/folders/${id}/${locale}`, {
269
+ method: 'post',
270
+ body: JSON.stringify(body),
271
+ headers: this.defaultHeaders,
272
+ })
273
+ this.checkStatus(res2)
274
+ return (await res2.json()) as FreshdeskFolder
275
+ }
276
+ if (httpError.code === 429) {
277
+ this.rateLimited = true
278
+ }
279
+ process.stdout.write(`Error processing id ${id} for locale ${locale}: ${httpError.message}\n`)
280
+ throw err
281
+ }
282
+ }
283
+
284
+ async updateArticleTranslation(
285
+ id: FreshdeskArticle['id'],
286
+ locale: string,
287
+ body: FreshdeskArticleCreate,
288
+ ): Promise<FreshdeskArticle | -1> {
289
+ if (this.rateLimited) {
290
+ process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
291
+ return -1
292
+ }
293
+ try {
294
+ const res = await fetch(`${this.baseUrl}/api/v2/solutions/articles/${id}/${locale}`, {
295
+ method: 'put',
296
+ body: JSON.stringify(body),
297
+ headers: this.defaultHeaders,
298
+ })
299
+ this.checkStatus(res)
300
+ return (await res.json()) as FreshdeskArticle
301
+ } catch (err) {
302
+ const httpError = err as HttpError
303
+ if (httpError.code === 404) {
304
+ // not found, try create instead
305
+ const res2 = await fetch(`${this.baseUrl}/api/v2/solutions/articles/${id}/${locale}`, {
306
+ method: 'post',
307
+ body: JSON.stringify(body),
308
+ headers: this.defaultHeaders,
309
+ })
310
+ this.checkStatus(res2)
311
+ return (await res2.json()) as FreshdeskArticle
312
+ }
313
+ if (httpError.code === 429) {
314
+ this.rateLimited = true
315
+ }
316
+ process.stdout.write(`Error processing id ${id} for locale ${locale}: ${httpError.message}\n`)
317
+ throw err
318
+ }
319
+ }
320
+ }
321
+
322
+ export default FreshdeskApi