소스 검색

Merge pull request #3589 from overleaf/i18next-scanner

Extract translation keys with i18next-scanner

GitOrigin-RevId: 5ba1b1e48c188290a0a462e830bc60b3d5b62c3d
Alf Eaton 5 년 전
부모
커밋
345ddb1f72

+ 0 - 139
services/web/frontend/extracted-translation-keys.json

@@ -1,139 +0,0 @@
-[
-  "ask_proj_owner_to_upgrade_for_longer_compiles",
-  "auto_compile",
-  "autocompile_disabled",
-  "autocompile_disabled_reason",
-  "autocomplete",
-  "autocomplete_references",
-  "back_to_your_projects",
-  "blocked_filename",
-  "cancel",
-  "clear_cached_files",
-  "clsi_maintenance",
-  "clsi_unavailable",
-  "collabs_per_proj",
-  "collapse",
-  "common",
-  "compile_error_description",
-  "compile_error_entry_description",
-  "compile_larger_projects",
-  "compile_mode",
-  "compile_terminated_by_user",
-  "compiling",
-  "conflicting_paths_found",
-  "copy",
-  "copy_project",
-  "copying",
-  "create",
-  "creating",
-  "delete",
-  "deleting",
-  "dismiss",
-  "dismiss_error_popup",
-  "done",
-  "download_file",
-  "download_pdf",
-  "duplicate_file",
-  "duplicate_paths_found",
-  "editing",
-  "error",
-  "expand",
-  "fast",
-  "file_already_exists",
-  "file_already_exists_in_this_location",
-  "file_outline",
-  "files_cannot_include_invalid_characters",
-  "find_out_more_about_the_file_outline",
-  "first_error_popup_label",
-  "following_paths_conflict",
-  "free_accounts_have_timeout_upgrade_to_increase",
-  "full_doc_history",
-  "full_screen",
-  "generic_something_went_wrong",
-  "give_feedback",
-  "go_to_error_location",
-  "headers",
-  "hide_outline",
-  "hotkeys",
-  "ignore_validation_errors",
-  "invalid_file_name",
-  "latex_error",
-  "learn_how_to_make_documents_compile_quickly",
-  "loading",
-  "log_entry_description",
-  "log_hint_extra_info",
-  "logs_pane_beta_message",
-  "logs_pane_beta_message_popup",
-  "main_file_not_found",
-  "math_display",
-  "math_inline",
-  "menu",
-  "n_errors",
-  "n_errors_plural",
-  "n_items",
-  "n_warnings",
-  "n_warnings_plural",
-  "navigate_log_source",
-  "navigation",
-  "new_file",
-  "new_folder",
-  "new_name",
-  "no_messages",
-  "normal",
-  "off",
-  "ok",
-  "on",
-  "other_logs_and_files",
-  "other_output_files",
-  "pdf_compile_in_progress_error",
-  "pdf_compile_rate_limit_hit",
-  "pdf_compile_try_again",
-  "pdf_rendering_error",
-  "please_compile_pdf_before_download",
-  "please_refresh",
-  "please_set_main_file",
-  "plus_upgraded_accounts_receive",
-  "proj_timed_out_reason",
-  "project_flagged_too_many_compiles",
-  "project_too_large",
-  "project_too_large_please_reduce",
-  "raw_logs",
-  "raw_logs_description",
-  "recompile",
-  "recompile_from_scratch",
-  "refresh",
-  "rename",
-  "review",
-  "run_syntax_check_now",
-  "send_first_message",
-  "server_error",
-  "show_outline",
-  "something_went_wrong_rendering_pdf",
-  "somthing_went_wrong_compiling",
-  "split_screen",
-  "start_free_trial",
-  "stop_compile",
-  "stop_on_validation_error",
-  "sure_you_want_to_delete",
-  "sync_to_dropbox",
-  "sync_to_github",
-  "terminated",
-  "timedout",
-  "toggle_compile_options_menu",
-  "toggle_output_files_list",
-  "too_recently_compiled",
-  "total_words",
-  "unlimited_projects",
-  "upgrade_for_longer_compiles",
-  "upload",
-  "validation_issue_description",
-  "validation_issue_entry_description",
-  "view_all_errors",
-  "view_logs",
-  "view_pdf",
-  "view_warnings",
-  "we_cant_find_any_sections_or_subsections_in_this_file",
-  "word_count",
-  "your_message",
-  "your_project_has_errors"
-]

+ 139 - 0
services/web/frontend/extracted-translations.json

@@ -0,0 +1,139 @@
+{
+  "ask_proj_owner_to_upgrade_for_longer_compiles": "",
+  "auto_compile": "",
+  "autocompile_disabled": "",
+  "autocompile_disabled_reason": "",
+  "autocomplete": "",
+  "autocomplete_references": "",
+  "back_to_your_projects": "",
+  "blocked_filename": "",
+  "cancel": "",
+  "chat": "",
+  "clear_cached_files": "",
+  "clsi_maintenance": "",
+  "clsi_unavailable": "",
+  "collabs_per_proj": "",
+  "collapse": "",
+  "common": "",
+  "compile_error_description": "",
+  "compile_error_entry_description": "",
+  "compile_larger_projects": "",
+  "compile_mode": "",
+  "compile_terminated_by_user": "",
+  "compiling": "",
+  "conflicting_paths_found": "",
+  "copy": "",
+  "copy_project": "",
+  "copying": "",
+  "create": "",
+  "creating": "",
+  "delete": "",
+  "deleting": "",
+  "dismiss": "",
+  "dismiss_error_popup": "",
+  "done": "",
+  "download_pdf": "",
+  "duplicate_file": "",
+  "editing": "",
+  "error": "",
+  "expand": "",
+  "fast": "",
+  "file_already_exists": "",
+  "file_already_exists_in_this_location": "",
+  "file_outline": "",
+  "files_cannot_include_invalid_characters": "",
+  "find_out_more_about_the_file_outline": "",
+  "first_error_popup_label": "",
+  "following_paths_conflict": "",
+  "free_accounts_have_timeout_upgrade_to_increase": "",
+  "full_doc_history": "",
+  "full_screen": "",
+  "generic_something_went_wrong": "",
+  "give_feedback": "",
+  "go_to_error_location": "",
+  "headers": "",
+  "hide_outline": "",
+  "hotkeys": "",
+  "ignore_validation_errors": "",
+  "invalid_file_name": "",
+  "learn_how_to_make_documents_compile_quickly": "",
+  "linked_file": "",
+  "loading": "",
+  "log_entry_description": "",
+  "log_hint_extra_info": "",
+  "logs_pane_beta_message": "",
+  "logs_pane_beta_message_popup": "",
+  "main_file_not_found": "",
+  "math_display": "",
+  "math_inline": "",
+  "menu": "",
+  "n_errors": "",
+  "n_errors_plural": "",
+  "n_items": "",
+  "n_items_plural": "",
+  "n_warnings": "",
+  "n_warnings_plural": "",
+  "navigate_log_source": "",
+  "navigation": "",
+  "new_file": "",
+  "new_folder": "",
+  "new_name": "",
+  "no_messages": "",
+  "normal": "",
+  "off": "",
+  "ok": "",
+  "on": "",
+  "other_logs_and_files": "",
+  "other_output_files": "",
+  "pdf_compile_in_progress_error": "",
+  "pdf_compile_rate_limit_hit": "",
+  "pdf_compile_try_again": "",
+  "pdf_rendering_error": "",
+  "please_compile_pdf_before_download": "",
+  "please_refresh": "",
+  "please_set_main_file": "",
+  "plus_upgraded_accounts_receive": "",
+  "proj_timed_out_reason": "",
+  "project_flagged_too_many_compiles": "",
+  "project_too_large": "",
+  "project_too_large_please_reduce": "",
+  "raw_logs": "",
+  "raw_logs_description": "",
+  "recompile": "",
+  "recompile_from_scratch": "",
+  "refresh": "",
+  "rename": "",
+  "review": "",
+  "run_syntax_check_now": "",
+  "send_first_message": "",
+  "server_error": "",
+  "show_outline": "",
+  "something_went_wrong_rendering_pdf": "",
+  "somthing_went_wrong_compiling": "",
+  "split_screen": "",
+  "start_free_trial": "",
+  "stop_compile": "",
+  "stop_on_validation_error": "",
+  "sure_you_want_to_delete": "",
+  "sync_to_dropbox": "",
+  "sync_to_github": "",
+  "terminated": "",
+  "timedout": "",
+  "toggle_compile_options_menu": "",
+  "toggle_output_files_list": "",
+  "too_recently_compiled": "",
+  "total_words": "",
+  "unlimited_projects": "",
+  "upgrade_for_longer_compiles": "",
+  "upload": "",
+  "validation_issue_description": "",
+  "validation_issue_entry_description": "",
+  "view_all_errors": "",
+  "view_logs": "",
+  "view_pdf": "",
+  "view_warnings": "",
+  "we_cant_find_any_sections_or_subsections_in_this_file": "",
+  "word_count": "",
+  "your_message": "",
+  "your_project_has_errors": ""
+}

+ 2 - 2
services/web/frontend/translations-loader.js

@@ -13,7 +13,7 @@ const Path = require('path')
 const SOURCE_PATH = Path.join(__dirname, '../locales')
 const EXTRACTED_TRANSLATIONS_PATH = Path.join(
   __dirname,
-  './extracted-translation-keys.json'
+  'extracted-translations.json'
 )
 
 module.exports = function translationsLoader() {
@@ -36,7 +36,7 @@ module.exports = function translationsLoader() {
 
 async function run(locale) {
   const json = await fs.readFile(EXTRACTED_TRANSLATIONS_PATH)
-  const keys = JSON.parse(json)
+  const keys = Object.keys(JSON.parse(json))
 
   const fallbackTranslations = await extract('en', keys)
   return extract(locale, keys, fallbackTranslations)

+ 27 - 0
services/web/i18next-scanner.config.js

@@ -0,0 +1,27 @@
+module.exports = {
+  input: [
+    'frontend/js/**/*.{js,jsx}',
+    'modules/**/*.{js,jsx}',
+    '!frontend/js/vendor/**'
+  ],
+  output: './',
+  options: {
+    sort: true,
+    func: {
+      list: ['t'],
+      extensions: ['.js', '.jsx']
+    },
+    trans: {
+      component: 'Trans',
+      i18nKey: 'i18nKey',
+      defaultsKey: 'defaults',
+      extensions: ['.js', '.jsx'],
+      fallbackKey: false
+    },
+    resource: {
+      savePath: 'frontend/extracted-translations.json',
+      jsonIndent: 2,
+      lineEnding: '\n'
+    }
+  }
+}

+ 588 - 0
services/web/package-lock.json

@@ -5805,6 +5805,21 @@
       "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
       "dev": true
     },
+    "acorn-class-fields": {
+      "version": "0.3.7",
+      "resolved": "https://registry.npmjs.org/acorn-class-fields/-/acorn-class-fields-0.3.7.tgz",
+      "integrity": "sha512-jdUWSFce0fuADUljmExz4TWpPkxmRW/ZCPRqeeUzbGf0vFUcpQYbyq52l75qGd0oSwwtAepeL6hgb/naRgvcKQ==",
+      "dev": true,
+      "requires": {
+        "acorn-private-class-elements": "^0.2.7"
+      }
+    },
+    "acorn-dynamic-import": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
+      "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
+      "dev": true
+    },
     "acorn-globals": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz",
@@ -5826,6 +5841,41 @@
       "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
       "dev": true
     },
+    "acorn-private-class-elements": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz",
+      "integrity": "sha512-+GZH2wOKNZOBI4OOPmzpo4cs6mW297sn6fgIk1dUI08jGjhAaEwvC39mN2gJAg2lmAQJ1rBkFqKWonL3Zz6PVA==",
+      "dev": true
+    },
+    "acorn-private-methods": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/acorn-private-methods/-/acorn-private-methods-0.3.3.tgz",
+      "integrity": "sha512-46oeEol3YFvLSah5m9hGMlNpxDBCEkdceJgf01AjqKYTK9r6HexKs2rgSbLK81pYjZZMonhftuUReGMlbbv05w==",
+      "dev": true,
+      "requires": {
+        "acorn-private-class-elements": "^0.2.7"
+      }
+    },
+    "acorn-stage3": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/acorn-stage3/-/acorn-stage3-4.0.0.tgz",
+      "integrity": "sha512-BR+LaADtA6GTB5prkNqWmlmCLYmkyW0whvSxdHhbupTaro2qBJ95fJDEiRLPUmiACGHPaYyeH9xmNJWdGfXRQw==",
+      "dev": true,
+      "requires": {
+        "acorn-class-fields": "^0.3.7",
+        "acorn-private-methods": "^0.3.3",
+        "acorn-static-class-features": "^0.2.4"
+      }
+    },
+    "acorn-static-class-features": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/acorn-static-class-features/-/acorn-static-class-features-0.2.4.tgz",
+      "integrity": "sha512-5X4mpYq5J3pdndLmIB0+WtFd/mKWnNYpuTlTzj32wUu/PMmEGOiayQ5UrqgwdBNiaZBtDDh5kddpP7Yg2QaQYA==",
+      "dev": true,
+      "requires": {
+        "acorn-private-class-elements": "^0.2.7"
+      }
+    },
     "acorn-walk": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
@@ -6156,6 +6206,15 @@
       "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=",
       "dev": true
     },
+    "append-buffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz",
+      "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=",
+      "dev": true,
+      "requires": {
+        "buffer-equal": "^1.0.0"
+      }
+    },
     "append-field": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
@@ -8682,6 +8741,12 @@
       "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
       "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
     },
+    "buffer-equal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
+      "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=",
+      "dev": true
+    },
     "buffer-equal-constant-time": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -9720,6 +9785,12 @@
       "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
       "integrity": "sha512-b2ijK6P2aNZYyFrb1B3a4kdAtaRueI+SpAKYNhR6i+R3xcF32vN1BLq8UoLU+L0NguGAg/9UQauaVOKrEij3sQ=="
     },
+    "clone-buffer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
+      "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=",
+      "dev": true
+    },
     "clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -9748,6 +9819,55 @@
         "mimic-response": "^1.0.0"
       }
     },
+    "clone-stats": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
+      "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=",
+      "dev": true
+    },
+    "cloneable-readable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz",
+      "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "process-nextick-args": "^2.0.0",
+        "readable-stream": "^2.3.5"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
     "cluster-key-slot": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
@@ -11311,6 +11431,12 @@
       "integrity": "sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw==",
       "dev": true
     },
+    "deepmerge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "dev": true
+    },
     "default-gateway": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
@@ -12283,6 +12409,12 @@
         }
       }
     },
+    "ensure-array": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ensure-array/-/ensure-array-1.0.0.tgz",
+      "integrity": "sha512-A+3Ntl5WS+GjDnHtC67dKIjw+IoGoeFdNvjn3ZfKEmZgWUz0nxBPE4W52QMCbGZsat0VwWskD5T6AEpe3T2d1g==",
+      "dev": true
+    },
     "ent": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
@@ -12303,6 +12435,12 @@
         "through": "~2.3.4"
       }
     },
+    "eol": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz",
+      "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==",
+      "dev": true
+    },
     "errno": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@@ -14585,6 +14723,16 @@
         "minipass": "^2.2.1"
       }
     },
+    "fs-mkdirp-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz",
+      "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "through2": "^2.0.3"
+      }
+    },
     "fs-readdir-recursive": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
@@ -14991,6 +15139,56 @@
         "@types/glob": "*"
       }
     },
+    "glob-stream": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz",
+      "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=",
+      "dev": true,
+      "requires": {
+        "extend": "^3.0.0",
+        "glob": "^7.1.1",
+        "glob-parent": "^3.1.0",
+        "is-negated-glob": "^1.0.0",
+        "ordered-read-streams": "^1.0.0",
+        "pumpify": "^1.3.5",
+        "readable-stream": "^2.1.5",
+        "remove-trailing-separator": "^1.0.1",
+        "to-absolute-glob": "^2.0.0",
+        "unique-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
     "glob-to-regexp": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -15392,6 +15590,15 @@
       "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==",
       "dev": true
     },
+    "gulp-sort": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz",
+      "integrity": "sha1-xnYqLx8N4KP8WVohWZ0/rI26Gso=",
+      "dev": true,
+      "requires": {
+        "through2": "^2.0.1"
+      }
+    },
     "gzip-size": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
@@ -16215,6 +16422,95 @@
       "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.0.2.tgz",
       "integrity": "sha512-h6n6+4k6EbYjtACPSslEkpf3Qf404QpENGb87auMSA77QBna9dUBWNAzG3jmqzK9cV4p8ZjsQTrwMzu+xyYaXw=="
     },
+    "i18next-scanner": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/i18next-scanner/-/i18next-scanner-3.0.0.tgz",
+      "integrity": "sha512-cm4Ch3VqicGZS8y+4xSvXoOsnE/iWhHZi6AZEyAgLLm3EDZ/eY21gDbLfbnwKVY6wCghzAEO9LfRNlxwTo8KMQ==",
+      "dev": true,
+      "requires": {
+        "acorn": "^8.0.4",
+        "acorn-dynamic-import": "^4.0.0",
+        "acorn-jsx": "^5.3.1",
+        "acorn-stage3": "^4.0.0",
+        "acorn-walk": "^8.0.0",
+        "chalk": "^4.1.0",
+        "clone-deep": "^4.0.0",
+        "commander": "^6.2.0",
+        "deepmerge": "^4.0.0",
+        "ensure-array": "^1.0.0",
+        "eol": "^0.9.1",
+        "esprima": "^4.0.0",
+        "gulp-sort": "^2.0.0",
+        "i18next": "*",
+        "lodash": "^4.0.0",
+        "parse5": "^6.0.0",
+        "sortobject": "^4.0.0",
+        "through2": "^4.0.0",
+        "vinyl": "^2.2.0",
+        "vinyl-fs": "^3.0.1"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "8.0.5",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz",
+          "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==",
+          "dev": true
+        },
+        "acorn-walk": {
+          "version": "8.0.2",
+          "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.2.tgz",
+          "integrity": "sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A==",
+          "dev": true
+        },
+        "commander": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+          "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+          "dev": true
+        },
+        "parse5": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+          "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.2.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          }
+        },
+        "through2": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+          "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+          "dev": true,
+          "requires": {
+            "readable-stream": "3"
+          }
+        }
+      }
+    },
     "iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -16659,6 +16955,16 @@
       "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz",
       "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg=="
     },
+    "is-absolute": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
+      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
+      "dev": true,
+      "requires": {
+        "is-relative": "^1.0.0",
+        "is-windows": "^1.0.1"
+      }
+    },
     "is-absolute-url": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
@@ -16916,6 +17222,12 @@
         }
       }
     },
+    "is-negated-glob": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
+      "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
+      "dev": true
+    },
     "is-negative-zero": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
@@ -17034,6 +17346,15 @@
         "has": "^1.0.1"
       }
     },
+    "is-relative": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
+      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
+      "dev": true,
+      "requires": {
+        "is-unc-path": "^1.0.0"
+      }
+    },
     "is-resolvable": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
@@ -17094,6 +17415,27 @@
       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
       "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
     },
+    "is-unc-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
+      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
+      "dev": true,
+      "requires": {
+        "unc-path-regex": "^0.1.2"
+      }
+    },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
+    "is-valid-glob": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
+      "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
+      "dev": true
+    },
     "is-whitespace-character": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
@@ -18467,6 +18809,15 @@
         "invert-kv": "^2.0.0"
       }
     },
+    "lead": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz",
+      "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=",
+      "dev": true,
+      "requires": {
+        "flush-write-stream": "^1.0.2"
+      }
+    },
     "less": {
       "version": "3.11.1",
       "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz",
@@ -20772,6 +21123,15 @@
         "sort-keys": "^1.0.0"
       }
     },
+    "now-and-later": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz",
+      "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.2"
+      }
+    },
     "npm-bundled": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
@@ -21523,6 +21883,47 @@
         }
       }
     },
+    "ordered-read-streams": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz",
+      "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
     "original": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
@@ -25581,6 +25982,27 @@
         "mdast-squeeze-paragraphs": "^4.0.0"
       }
     },
+    "remove-bom-buffer": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz",
+      "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==",
+      "dev": true,
+      "requires": {
+        "is-buffer": "^1.1.5",
+        "is-utf8": "^0.2.1"
+      }
+    },
+    "remove-bom-stream": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz",
+      "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=",
+      "dev": true,
+      "requires": {
+        "remove-bom-buffer": "^3.0.0",
+        "safe-buffer": "^5.1.0",
+        "through2": "^2.0.3"
+      }
+    },
     "remove-trailing-separator": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -25675,6 +26097,12 @@
       "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
       "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="
     },
+    "replace-ext": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz",
+      "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==",
+      "dev": true
+    },
     "request": {
       "version": "2.88.2",
       "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@@ -25900,6 +26328,15 @@
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
       "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ=="
     },
+    "resolve-options": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz",
+      "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=",
+      "dev": true,
+      "requires": {
+        "value-or-function": "^3.0.0"
+      }
+    },
     "resolve-url": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -27266,6 +27703,12 @@
         "is-plain-obj": "^1.0.0"
       }
     },
+    "sortobject": {
+      "version": "4.14.0",
+      "resolved": "https://registry.npmjs.org/sortobject/-/sortobject-4.14.0.tgz",
+      "integrity": "sha512-CpH+1tXhO2IOZU7rlpuoBAil/1oxcd5kYIvwKcNd03/3Y9ls9YYe7jbJ6kNW70A/sKJbMBBHTzy68IO7KIM58g==",
+      "dev": true
+    },
     "source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -29176,6 +29619,16 @@
         }
       }
     },
+    "through2-filter": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
+      "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==",
+      "dev": true,
+      "requires": {
+        "through2": "~2.0.0",
+        "xtend": "~4.0.0"
+      }
+    },
     "thunky": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -29241,6 +29694,16 @@
       "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
       "dev": true
     },
+    "to-absolute-glob": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
+      "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=",
+      "dev": true,
+      "requires": {
+        "is-absolute": "^1.0.0",
+        "is-negated-glob": "^1.0.0"
+      }
+    },
     "to-array": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@@ -29336,6 +29799,15 @@
         "loader-utils": "^1.0.0"
       }
     },
+    "to-through": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz",
+      "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=",
+      "dev": true,
+      "requires": {
+        "through2": "^2.0.3"
+      }
+    },
     "toggle-selection": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
@@ -29578,6 +30050,12 @@
       "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
       "dev": true
     },
+    "unc-path-regex": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+      "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
+      "dev": true
+    },
     "uncontrollable": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz",
@@ -29737,6 +30215,16 @@
         "imurmurhash": "^0.1.4"
       }
     },
+    "unique-stream": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz",
+      "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==",
+      "dev": true,
+      "requires": {
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "through2-filter": "^3.0.0"
+      }
+    },
     "unique-string": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
@@ -30245,6 +30733,12 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "value-or-function": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",
+      "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=",
+      "dev": true
+    },
     "vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -30302,6 +30796,100 @@
         "unist-util-stringify-position": "^2.0.0"
       }
     },
+    "vinyl": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
+      "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
+      "dev": true,
+      "requires": {
+        "clone": "^2.1.1",
+        "clone-buffer": "^1.0.0",
+        "clone-stats": "^1.0.0",
+        "cloneable-readable": "^1.0.0",
+        "remove-trailing-separator": "^1.0.1",
+        "replace-ext": "^1.0.0"
+      },
+      "dependencies": {
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+          "dev": true
+        }
+      }
+    },
+    "vinyl-fs": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz",
+      "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==",
+      "dev": true,
+      "requires": {
+        "fs-mkdirp-stream": "^1.0.0",
+        "glob-stream": "^6.1.0",
+        "graceful-fs": "^4.0.0",
+        "is-valid-glob": "^1.0.0",
+        "lazystream": "^1.0.0",
+        "lead": "^1.0.0",
+        "object.assign": "^4.0.4",
+        "pumpify": "^1.3.5",
+        "readable-stream": "^2.3.3",
+        "remove-bom-buffer": "^3.0.0",
+        "remove-bom-stream": "^1.2.0",
+        "resolve-options": "^1.1.0",
+        "through2": "^2.0.0",
+        "to-through": "^2.0.0",
+        "value-or-function": "^3.0.0",
+        "vinyl": "^2.0.0",
+        "vinyl-sourcemap": "^1.1.0"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "vinyl-sourcemap": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz",
+      "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=",
+      "dev": true,
+      "requires": {
+        "append-buffer": "^1.0.2",
+        "convert-source-map": "^1.5.0",
+        "graceful-fs": "^4.1.6",
+        "normalize-path": "^2.1.1",
+        "now-and-later": "^2.0.0",
+        "remove-bom-buffer": "^3.0.0",
+        "vinyl": "^2.0.0"
+      }
+    },
     "vm-browserify": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",

+ 2 - 0
services/web/package.json

@@ -33,6 +33,7 @@
     "format:styles:fix": "prettier --write $PWD/'**/*.less'",
     "lint": "eslint --max-warnings 0 --format unix .",
     "lint:fix": "eslint --fix .",
+    "extract-translations": "i18next-scanner",
     "migrations": "east",
     "storybook": "start-storybook -p 6006"
   },
@@ -199,6 +200,7 @@
     "file-loader": "^5.0.2",
     "glob": "^7.1.6",
     "handlebars-loader": "^1.7.1",
+    "i18next-scanner": "^3.0.0",
     "jsdom": "^16.2.2",
     "jsdom-global": "^3.0.2",
     "karma": "^5.0.2",