check-locales.cjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env node
  2. // This file does a few things to ensure that the Locales are present and valid:
  3. // - Ensures that the name of the locale exists in the language list
  4. // - Ensures that each locale contains the translations used in the application
  5. // - Ensures that there are no unused translations in the locale files
  6. // - Also checks the error messages returned by the backend
  7. const allLocales = [
  8. ["en", "en-US"],
  9. ["de", "de-DE"],
  10. ["es", "es-ES"],
  11. ["it", "it-IT"],
  12. ["ja", "ja-JP"],
  13. ["nl", "nl-NL"],
  14. ["pl", "pl-PL"],
  15. ["ru", "ru-RU"],
  16. ["sk", "sk-SK"],
  17. ["vi", "vi-VN"],
  18. ["zh", "zh-CN"],
  19. ["ko", "ko-KR"],
  20. ["bg", "bg-BG"],
  21. ];
  22. const ignoreUnused = [
  23. /^.*$/,
  24. ];
  25. const { spawnSync } = require("child_process");
  26. const fs = require("fs");
  27. const tmp = require("tmp");
  28. // Parse backend errors
  29. const BACKEND_ERRORS_FILE = "../backend/internal/errors/errors.go";
  30. const BACKEND_ERRORS = [];
  31. /*
  32. try {
  33. const backendErrorsContent = fs.readFileSync(BACKEND_ERRORS_FILE, "utf8");
  34. const backendErrorsContentRes = [
  35. ...backendErrorsContent.matchAll(/(?:errors|eris)\.New\("([^"]+)"\)/g),
  36. ];
  37. backendErrorsContentRes.map((item) => {
  38. BACKEND_ERRORS.push("error." + item[1]);
  39. return null;
  40. });
  41. } catch (err) {
  42. console.log("\x1b[31m%s\x1b[0m", err);
  43. process.exit(1);
  44. }
  45. */
  46. // get all translations used in frontend code
  47. const tmpobj = tmp.fileSync({ postfix: ".json" });
  48. spawnSync("yarn", ["locale-extract", "--out-file", tmpobj.name]);
  49. const allLocalesInProject = require(tmpobj.name);
  50. // get list og language names and locales
  51. const langList = require("./src/locale/src/lang-list.json");
  52. // store a list of all validation errors
  53. const allErrors = [];
  54. const allWarnings = [];
  55. const allKeys = [];
  56. const checkLangList = (fullCode) => {
  57. const key = "locale-" + fullCode;
  58. if (typeof langList[key] === "undefined") {
  59. allErrors.push(
  60. "ERROR: `" + key + "` language does not exist in lang-list.json",
  61. );
  62. }
  63. };
  64. const compareLocale = (locale) => {
  65. const projectLocaleKeys = Object.keys(allLocalesInProject);
  66. // Check that locale contains the items used in the codebase
  67. projectLocaleKeys.map((key) => {
  68. if (typeof locale.data[key] === "undefined") {
  69. allErrors.push(
  70. "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`",
  71. );
  72. }
  73. return null;
  74. });
  75. // Check that locale contains all error.* items
  76. BACKEND_ERRORS.forEach((key) => {
  77. if (typeof locale.data[key] === "undefined") {
  78. allErrors.push(
  79. "ERROR: `" + locale[0] + "` does not contain item: `" + key + "`",
  80. );
  81. }
  82. return null;
  83. });
  84. // Check that locale does not contain items not used in the codebase
  85. const localeKeys = Object.keys(locale.data);
  86. localeKeys.map((key) => {
  87. let ignored = false;
  88. ignoreUnused.map((regex) => {
  89. if (key.match(regex)) {
  90. ignored = true;
  91. }
  92. return null;
  93. });
  94. if (!ignored && typeof allLocalesInProject[key] === "undefined") {
  95. // ensure this key doesn't exist in the backend errors either
  96. if (!BACKEND_ERRORS.includes(key)) {
  97. allErrors.push(
  98. "ERROR: `" + locale[0] + "` contains unused item: `" + key + "`",
  99. );
  100. }
  101. }
  102. // Add this key to allKeys
  103. if (allKeys.indexOf(key) === -1) {
  104. allKeys.push(key);
  105. }
  106. return null;
  107. });
  108. };
  109. // Checks for any keys missing from this locale, that
  110. // have been defined in any other locales
  111. const checkForMissing = (locale) => {
  112. allKeys.forEach((key) => {
  113. if (typeof locale.data[key] === "undefined") {
  114. allWarnings.push(
  115. "WARN: `" + locale[0] + "` does not contain item: `" + key + "`",
  116. );
  117. }
  118. return null;
  119. });
  120. };
  121. // Local all locale data
  122. allLocales.map((locale, idx) => {
  123. checkLangList(locale[1]);
  124. allLocales[idx].data = require("./src/locale/src/" + locale[0] + ".json");
  125. return null;
  126. });
  127. // Verify all locale data
  128. allLocales.map((locale) => {
  129. compareLocale(locale);
  130. checkForMissing(locale);
  131. return null;
  132. });
  133. if (allErrors.length) {
  134. allErrors.map((err) => {
  135. console.log("\x1b[31m%s\x1b[0m", err);
  136. return null;
  137. });
  138. }
  139. if (allWarnings.length) {
  140. allWarnings.map((err) => {
  141. console.log("\x1b[33m%s\x1b[0m", err);
  142. return null;
  143. });
  144. }
  145. if (allErrors.length) {
  146. process.exit(1);
  147. }
  148. console.log("\x1b[32m%s\x1b[0m", "Locale check passed");
  149. process.exit(0);