check-locales.cjs 4.1 KB

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