VYPR
Medium severity6.5GHSA Advisory· Published May 8, 2026· Updated May 12, 2026

CVE-2026-41885

CVE-2026-41885

Description

i18next-locize-backend is a simple i18next backend for locize.com which can be used in Node.js, in the browser and for Deno. Prior to version 9.0.2, i18next-locize-backend interpolates lng, ns, projectId, and version directly into the configured loadPath / privatePath / addPath / updatePath / getLanguagesPath URL templates with no path-component validation and no encoding. When an application exposes any of these values to user-controlled input (?lng= / ?ns= query parameters via i18next-browser-languagedetector, cookies, request headers, or a URL-derived projectId), a crafted value can change the structure of the outgoing request URL. Affected call sites in lib/index.js (pre-patch): the interpolate() helper is used at the five URL-build sites — _readAny/read (line 415 for private, 426 for public), getLanguages (lines 271 and 296), and writePage (lines 616 and 622) for the missing-key and update POST paths. The helper interpolate in lib/utils.js substitutes raw values with no encoding. This issue has been patched in version 9.0.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
i18next-locize-backendnpm
< 9.0.29.0.2

Affected products

1

Patches

1
8f81ad4707aa

security: hardening for 9.0.2

https://github.com/locize/i18next-locize-backendAdriano RaianoApr 18, 2026via ghsa
11 files changed · +369 35
  • CHANGELOG.md+10 0 modified
    @@ -1,3 +1,13 @@
    +### 9.0.2
    +
    +Security release — all issues found via an internal audit. GHSA advisory filed after release.
    +
    +- security: refuse to build request URLs when `lng`, `ns`, `projectId`, or `version` values contain path-traversal (`..`), path separators (`/`, `\`), URL-structure characters (`?`, `#`, `%`, `@`, whitespace), control characters, prototype keys (`__proto__`, `constructor`, `prototype`), or exceed 128 chars. Prevents path traversal / URL injection via attacker-controlled option values (same class of fix as `i18next-http-backend@3.0.5`) (GHSA-TBD)
    +- security: guard `interpolate` against prototype-chain lookups — a polluted `Object.prototype.__proto__` no longer leaks into URL substitution
    +- security: replace `for...in` iteration over the `defaults` source with `Object.keys()` + explicit prototype-key guard
    +- chore: remove unused `.coveralls.yml`
    +- chore: ignore `.env*` and `*.pem`/`*.key` files in `.gitignore`
    +
     ### 9.0.1
     
     - improve handling for inexisting projects
    
  • .coveralls.yml+0 1 removed
    @@ -1 +0,0 @@
    -repo_token: dkgQM5oEcrciHIlCo1DJtSMyW7bRdv9Vb
    
  • .github/workflows/node.yml+1 1 modified
    @@ -11,7 +11,7 @@ jobs:
         runs-on: ${{ matrix.os }}
         strategy:
           matrix:
    -        node: [ '24.x', '22.x', '20.x', '18.x' ]
    +        node: [ '24.x', '22.x', '20.x' ]
             # os: [ubuntu-latest, windows-latest, macOS-latest]
             os: [ubuntu-latest]
         steps:
    
  • .gitignore+8 1 modified
    @@ -21,4 +21,11 @@ bin/**/*
     
     package-lock.json
     cjs
    -esm
    \ No newline at end of file
    +esm
    +
    +# Secrets & credentials
    +.env
    +.env.*
    +!.env.example
    +*.pem
    +*.key
    
  • i18nextLocizeBackend.js+110 14 modified
    @@ -270,14 +270,18 @@ var I18NextLocizeBackend = function () {
             callback(new Error(isMissing));
             return deferred;
           }
    -      var url = (0, _utils.interpolate)(this.options.getLanguagesPath, {
    +      var url = (0, _utils.interpolateUrl)(this.options.getLanguagesPath, {
             projectId: this.options.projectId
           });
    +      if (url == null) {
    +        callback(new Error('i18next-locize-backend: unsafe projectId — refusing to build request URL for projectId=' + (0, _utils.sanitizeLogValue)(String(this.options.projectId))));
    +        return deferred;
    +      }
           if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
             this.isProjectNotExisting = true;
           }
           if (this.isProjectNotExisting) {
    -        callback(new Error(this.isProjectNotExistingErrorMessage || "locize project ".concat(this.options.projectId, " does not exist!")));
    +        callback(new Error(this.isProjectNotExistingErrorMessage || "Locize project ".concat(this.options.projectId, " does not exist!")));
             return deferred;
           }
           this.getLanguagesCalls = this.getLanguagesCalls || [];
    @@ -286,16 +290,17 @@ var I18NextLocizeBackend = function () {
           this.loadUrl({}, url, function (err, ret, info) {
             if (!_this3.somethingLoaded && info && info.resourceNotExisting) {
               _this3.isProjectNotExisting = true;
    -          var errMsg = "locize project ".concat(_this3.options.projectId, " does not exist!");
    +          var errMsg = "Locize project ".concat(_this3.options.projectId, " does not exist!");
               _this3.isProjectNotExistingErrorMessage = errMsg;
               var cdnTypeAlt = _this3.options.cdnType === 'standard' ? 'pro' : 'standard';
               var otherEndpointApiPaths = getApiPaths(cdnTypeAlt);
    -          var urlAlt = (0, _utils.interpolate)(otherEndpointApiPaths.getLanguagesPath, {
    +          var urlAlt = (0, _utils.interpolateUrl)(otherEndpointApiPaths.getLanguagesPath, {
                 projectId: _this3.options.projectId
               });
    +          if (urlAlt == null) return;
               _this3.loadUrl({}, urlAlt, function (errAlt, retAlt, infoAlt) {
                 if (!errAlt && retAlt && (!infoAlt || !infoAlt.resourceNotExisting)) {
    -              errMsg += " It seems you're using the wrong cdnType. Your locize project is configured to use \"".concat(cdnTypeAlt, "\" but here you've configured \"").concat(_this3.options.cdnType, "\".");
    +              errMsg += " It seems you're using the wrong cdnType. Your Locize project is configured to use \"".concat(cdnTypeAlt, "\" but here you've configured \"").concat(_this3.options.cdnType, "\".");
                   _this3.isProjectNotExistingErrorMessage = errMsg;
                 } else if (!_this3.somethingLoaded && infoAlt && infoAlt.resourceNotExisting) {
                   _this3.isProjectNotExisting = true;
    @@ -425,7 +430,7 @@ var I18NextLocizeBackend = function () {
           if (this.options.private) {
             var isMissing = (0, _utils.isMissingOption)(this.options, ['projectId', 'version', 'apiKey']);
             if (isMissing) return callback(new Error(isMissing), false);
    -        url = (0, _utils.interpolate)(this.options.privatePath, {
    +        url = (0, _utils.interpolateUrl)(this.options.privatePath, {
               lng: language,
               ns: namespace,
               projectId: this.options.projectId,
    @@ -437,24 +442,27 @@ var I18NextLocizeBackend = function () {
           } else {
             var _isMissing = (0, _utils.isMissingOption)(this.options, ['projectId', 'version']);
             if (_isMissing) return callback(new Error(_isMissing), false);
    -        url = (0, _utils.interpolate)(this.options.loadPath, {
    +        url = (0, _utils.interpolateUrl)(this.options.loadPath, {
               lng: language,
               ns: namespace,
               projectId: this.options.projectId,
               version: this.options.version
             });
           }
    +      if (url == null) {
    +        return callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to build request URL for lng=' + (0, _utils.sanitizeLogValue)(String(language)) + ' ns=' + (0, _utils.sanitizeLogValue)(String(namespace))), false);
    +      }
           if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
             this.isProjectNotExisting = true;
           }
           if (this.isProjectNotExisting) {
    -        var err = new Error(this.isProjectNotExistingErrorMessage || "locize project ".concat(this.options.projectId, " does not exist!"));
    +        var err = new Error(this.isProjectNotExistingErrorMessage || "Locize project ".concat(this.options.projectId, " does not exist!"));
             if (logger) logger.error(err.message);
             if (callback) callback(err);
             return;
           }
           if (this.warnedLanguages && this.warnedLanguages.indexOf(language) > -1) {
    -        var _err = new Error("Will not continue to load language \"".concat(language, "\" since it is not available in locize project ").concat(this.options.projectId, "!"));
    +        var _err = new Error("Will not continue to load language \"".concat(language, "\" since it is not available in Locize project ").concat(this.options.projectId, "!"));
             if (logger) logger.error(_err.message);
             if (callback) callback(_err);
             return;
    @@ -473,7 +481,7 @@ var I18NextLocizeBackend = function () {
                   if (_this6.warnedLanguages && _this6.warnedLanguages.indexOf(language) > -1) return;
                   _this6.warnedLanguages || (_this6.warnedLanguages = []);
                   _this6.warnedLanguages.push(language);
    -              if (logger) logger.error("Language \"".concat(language, "\" is not available in locize project ").concat(_this6.options.projectId, "!"));
    +              if (logger) logger.error("Language \"".concat(language, "\" is not available in Locize project ").concat(_this6.options.projectId, "!"));
                 });
               }, randomizeTimeout(_this6.options.checkForProjectTimeout));
             }
    @@ -602,18 +610,24 @@ var I18NextLocizeBackend = function () {
       }, {
         key: "writePage",
         value: function writePage(lng, namespace, missings, callback) {
    -      var missingUrl = (0, _utils.interpolate)(this.options.addPath, {
    +      var missingUrl = (0, _utils.interpolateUrl)(this.options.addPath, {
             lng: lng,
             ns: namespace,
             projectId: this.options.projectId,
             version: this.options.version
           });
    -      var updatesUrl = (0, _utils.interpolate)(this.options.updatePath, {
    +      var updatesUrl = (0, _utils.interpolateUrl)(this.options.updatePath, {
             lng: lng,
             ns: namespace,
             projectId: this.options.projectId,
             version: this.options.version
           });
    +      if (missingUrl == null || updatesUrl == null) {
    +        if (typeof callback === 'function') {
    +          callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to persist missing keys for lng=' + (0, _utils.sanitizeLogValue)(String(lng)) + ' ns=' + (0, _utils.sanitizeLogValue)(String(namespace))));
    +        }
    +        return;
    +      }
           var hasMissing = false;
           var hasUpdates = false;
           var payloadMissing = {};
    @@ -910,28 +924,67 @@ module.exports = exports.default;
     Object.defineProperty(exports, "__esModule", {
       value: true
     });
    +exports.UNSAFE_KEYS = void 0;
     exports.debounce = debounce;
     exports.defaults = defaults;
     exports.defer = defer;
     exports.getPath = getPath;
     exports.interpolate = interpolate;
    +exports.interpolateUrl = interpolateUrl;
     exports.isMissingOption = isMissingOption;
    +exports.isSafeUrlSegment = isSafeUrlSegment;
     exports.optionExist = optionExist;
     exports.pushPath = pushPath;
    +exports.redactUrlCredentials = redactUrlCredentials;
    +exports.sanitizeLogValue = sanitizeLogValue;
     exports.setPath = setPath;
    +function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
    +function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
    +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
     var arr = [];
     var each = arr.forEach;
     var slice = arr.slice;
    +var UNSAFE_KEYS = exports.UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
     function defaults(obj) {
       each.call(slice.call(arguments, 1), function (source) {
         if (source) {
    -      for (var prop in source) {
    +      for (var _i = 0, _Object$keys = Object.keys(source); _i < _Object$keys.length; _i++) {
    +        var prop = _Object$keys[_i];
    +        if (UNSAFE_KEYS.indexOf(prop) > -1) continue;
             if (obj[prop] === undefined) obj[prop] = source[prop];
           }
         }
       });
       return obj;
     }
    +function isSafeUrlSegment(v) {
    +  if (typeof v !== 'string') return false;
    +  if (v.length === 0 || v.length > 128) return false;
    +  if (UNSAFE_KEYS.indexOf(v) > -1) return false;
    +  if (v.indexOf('..') > -1) return false;
    +  if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
    +  if (/[?#%\s@]/.test(v)) return false;
    +  if (/[\x00-\x1F\x7F]/.test(v)) return false;
    +  return true;
    +}
    +function sanitizeLogValue(v) {
    +  if (typeof v !== 'string') return v;
    +  return v.replace(/[\r\n\x00-\x1F\x7F]/g, ' ');
    +}
    +function redactUrlCredentials(u) {
    +  if (typeof u !== 'string' || u.length === 0) return u;
    +  try {
    +    var parsed = new URL(u);
    +    if (parsed.username || parsed.password) {
    +      parsed.username = '';
    +      parsed.password = '';
    +      return parsed.toString();
    +    }
    +    return u;
    +  } catch (e) {
    +    return u.replace(/(\/\/)[^/@\s]+@/g, '$1');
    +  }
    +}
     function debounce(func, wait, immediate) {
       var timeout;
       return function () {
    @@ -1001,11 +1054,54 @@ function interpolate(str, data, lng) {
         if (typeof value !== 'string') value = makeString(value);
         if (!value) value = '';
         value = regexSafe(value);
    -    str = str.replace(match[0], data[value] || value);
    +    var subst = UNSAFE_KEYS.indexOf(value) > -1 ? value : data[value] || value;
    +    str = str.replace(match[0], subst);
         regexp.lastIndex = 0;
       }
       return str;
     }
    +function interpolateUrl(str, data) {
    +  var match;
    +  var unsafe = false;
    +  while (match = regexp.exec(str)) {
    +    var key = match[1].trim();
    +    if (UNSAFE_KEYS.indexOf(key) > -1) {
    +      regexp.lastIndex = 0;
    +      continue;
    +    }
    +    var raw = data[key];
    +    if (raw == null) {
    +      regexp.lastIndex = 0;
    +      continue;
    +    }
    +    var value = makeString(raw);
    +    var segments = value.split('+');
    +    var segmentsOk = true;
    +    var _iterator = _createForOfIteratorHelper(segments),
    +      _step;
    +    try {
    +      for (_iterator.s(); !(_step = _iterator.n()).done;) {
    +        var seg = _step.value;
    +        if (!isSafeUrlSegment(seg)) {
    +          segmentsOk = false;
    +          break;
    +        }
    +      }
    +    } catch (err) {
    +      _iterator.e(err);
    +    } finally {
    +      _iterator.f();
    +    }
    +    if (!segmentsOk) {
    +      unsafe = true;
    +      break;
    +    }
    +    str = str.replace(match[0], segments.join('+'));
    +    regexp.lastIndex = 0;
    +  }
    +  regexp.lastIndex = 0;
    +  return unsafe ? null : str;
    +}
     function isMissingOption(obj, props) {
       return props.reduce(function (mem, p) {
         if (mem) return mem;
    
  • i18nextLocizeBackend.min.js+1 1 modified
    @@ -1 +1 @@
    -!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).i18nextLocizeBackend=e()}(function(){return function n(i,s,r){function a(t,e){if(!s[t]){if(!i[t]){var o="function"==typeof require&&require;if(!e&&o)return o(t,!0);if(c)return c(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}o=s[t]={exports:{}},i[t][0].call(o.exports,function(e){return a(i[t][1][e]||e)},o,o.exports,n,i,s,r)}return s[t].exports}for(var c="function"==typeof require&&require,e=0;e<r.length;e++)a(r[e]);return a}({1:[function(e,t,o){Object.defineProperty(o,"__esModule",{value:!0}),o.default=void 0;var l=e("./utils.js"),f=(e=e("./request.js"))&&e.__esModule?e:{default:e};function d(e){return(d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function n(e,t){for(var o=0;o<t.length;o++){var n=t[o];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,(e=>(e=((e,t)=>{if("object"!=d(e)||!e)return e;var o=e[Symbol.toPrimitive];if(void 0===o)return("string"===t?String:Number)(e);if("object"!=d(o=o.call(e,t||"default")))return o;throw new TypeError("@@toPrimitive must return a primitive value.")})(e,"string"),"symbol"==d(e)?e:e+""))(n.key),n)}}function p(e){return{loadPath:"https://api".concat("standard"===(e=e||"standard")?".lite":"",".locize.app/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),privatePath:"https://api".concat("standard"===e?".lite":"",".locize.app/private/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),getLanguagesPath:"https://api".concat("standard"===e?".lite":"",".locize.app/languages/{{projectId}}"),addPath:"https://api".concat("standard"===e?".lite":"",".locize.app/missing/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),updatePath:"https://api".concat("standard"===e?".lite":"",".locize.app/update/{{projectId}}/{{version}}/{{lng}}/{{ns}}")}}try{var g="undefined"!=typeof window&&null!==window.localStorage,i="notExistingLocizeProject";window.localStorage.setItem(i,"foo"),window.localStorage.removeItem(i)}catch(e){g=!1}function h(e,t,o){var n={};return t.authorize&&t.apiKey&&(n.Authorization=t.apiKey),(o||t.setContentTypeJSON)&&(n["Content-Type"]="application/json"),{method:o?"POST":"GET",url:e,headers:n,body:o}}function v(e,t,o){if(1===e.request.length)try{var n=e.request(t);n&&"function"==typeof n.then?n.then(function(e){return o(null,e)}).catch(o):o(null,n)}catch(e){o(e)}else e.request(t,o)}function c(e){var t=.25*e,o=Math.max(0,e-t),e=e+t;return Math.floor(o+Math.random()*(e-o))}e=function e(t){var o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{},i=3<arguments.length?arguments[3]:void 0,s=this,r=e;if(!(s instanceof r))throw new TypeError("Cannot call a class as a function");this.services=t,this.options=o,this.allOptions=n,this.type="backend",t&&t.projectId?this.init(null,t,n,o):this.init(t,o,n,i)},(i=[{key:"init",value:function(e){var o,n=this,i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},t=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{},s=3<arguments.length?arguments[3]:void 0,r=(!i.referenceLng&&t.fallbackLng&&Array.isArray(t.fallbackLng)&&"dev"!==t.fallbackLng[0]&&(i.referenceLng=t.fallbackLng[0]),this.services=e,(0,l.defaults)({},i)),a=(0,l.defaults)(i,this.options||{}),c=(c=a.cdnType,(0,l.defaults)({cdnType:c=c||"standard",noCache:!1,referenceLng:"en",crossDomain:!0,setContentTypeJSON:!1,version:"latest",private:!1,translatedPercentageThreshold:.9,failLoadingOnEmptyJSON:!1,allowedAddOrUpdateHosts:["localhost"],onSaved:!1,reloadInterval:"undefined"==typeof window&&36e5,checkForProjectTimeout:3e3,storageExpiration:36e5,writeDebounce:5e3,useCacheLayer:"undefined"==typeof window},p(c))),u=(a.reloadInterval&&a.reloadInterval<3e5&&(console.warn("Your configured reloadInterval option is to low."),a.reloadInterval=c.reloadInterval),this.options=(0,l.defaults)(i,this.options||{},c),this.allOptions=t,this.somethingLoaded=!1,this.isProjectNotExisting=!1,this.storage=(o=this.options.storageExpiration,a=function(){},c=function(){},g?(a=function(e){window.localStorage.setItem("notExistingLocizeProject_".concat(e),Date.now())},c=function(e){var t=window.localStorage.getItem("notExistingLocizeProject_".concat(e));return!(!t||Date.now()-t>o&&(window.localStorage.removeItem("notExistingLocizeProject_".concat(e)),1))}):"undefined"!=typeof document&&(a=function(e){var t=new Date,t=(t.setTime(t.getTime()+o),"; expires=".concat(t.toGMTString())),e="notExistingLocizeProject_".concat(e);try{document.cookie="".concat(e,"=").concat(Date.now()).concat(t,";path=/")}catch(e){}},c=function(e){var e="notExistingLocizeProject_".concat(e),t="".concat(e,"=");try{for(var o=document.cookie.split(";"),n=0;n<o.length;n++){for(var i=o[n];" "===i.charAt(0);)i=i.substring(1,i.length);if(0===i.indexOf(t))return!0}}catch(e){}return!1}),{setProjectNotExisting:a,isProjectNotExisting:c}),p(this.options.cdnType)),a=(Object.keys(u).forEach(function(e){r[e]||(n.options[e]=u[e])}),t.debug&&void 0===r.noCache&&"standard"===this.options.cdnType&&(this.options.noCache=!0),this.options.noCache&&"standard"!==this.options.cdnType&&console.warn("The 'noCache' option is not available for 'cdnType' '".concat(this.options.cdnType,"'!")),"undefined"!=typeof window&&window.location&&window.location.hostname);a?(this.isAddOrUpdateAllowed="function"==typeof this.options.allowedAddOrUpdateHosts?this.options.allowedAddOrUpdateHosts(a):-1<this.options.allowedAddOrUpdateHosts.indexOf(a),e&&e.logger&&(t.saveMissing||t.updateMissing)&&(this.isAddOrUpdateAllowed?"localhost"!==a&&e.logger.warn('locize-backend: you are using the save or update missings feature from this host "'.concat(a,'".\nMake sure you will not use it in production!\nhttps://www.locize.com/docs/going-to-production')):e.logger.warn("function"==typeof this.options.allowedAddOrUpdateHosts?'locize-backend: will not save or update missings because allowedAddOrUpdateHosts returned false for the host "'.concat(a,'".'):'locize-backend: will not save or update missings because the host "'.concat(a,'" was not in the list of allowedAddOrUpdateHosts: ').concat(this.options.allowedAddOrUpdateHosts.join(", ")," (matches need to be exact).")))):this.isAddOrUpdateAllowed=!0,"function"==typeof s&&this.getOptions(function(e,t,o){if(e)return s(e);n.options.referenceLng=i.referenceLng||t.referenceLng||n.options.referenceLng,s(null,t,o)}),this.queuedWrites={pending:{}},this.debouncedProcess=(0,l.debounce)(this.process,this.options.writeDebounce),this.interval&&clearInterval(this.interval),this.options.reloadInterval&&this.options.projectId&&(this.interval=setInterval(function(){return n.reload()},this.options.reloadInterval),"object"===d(this.interval))&&"function"==typeof this.interval.unref&&this.interval.unref()}},{key:"reload",value:function(){var t,e,o=this,n=this.services||{logger:console},i=n.backendConnector,s=n.languageUtils,r=n.logger;!i||(n=i.language)&&"cimode"===n.toLowerCase()||(t=[],(e=function(e){s.toResolveHierarchy(e).forEach(function(e){t.indexOf(e)<0&&t.push(e)})})(n),this.allOptions.preload&&this.allOptions.preload.forEach(e),t.forEach(function(n){o.allOptions.ns.forEach(function(o){i.read(n,o,"read",null,null,function(e,t){e&&r.warn("loading namespace ".concat(o," for language ").concat(n," failed"),e),!e&&t&&r.log("loaded namespace ".concat(o," for language ").concat(n),t),i.loaded("".concat(n,"|").concat(o),e,t)})})}))}},{key:"getLanguages",value:function(e){var o,r=this,t=(e||(o=(0,l.defer)(),e=function(e,t){if(e)return o.reject(e);o.resolve(t)}),(0,l.isMissingOption)(this.options,["projectId"]));return t?e(new Error(t)):(t=(0,l.interpolate)(this.options.getLanguagesPath,{projectId:this.options.projectId}),!this.isProjectNotExisting&&this.storage.isProjectNotExisting(this.options.projectId)&&(this.isProjectNotExisting=!0),this.isProjectNotExisting?e(new Error(this.isProjectNotExistingErrorMessage||"locize project ".concat(this.options.projectId," does not exist!"))):(this.getLanguagesCalls=this.getLanguagesCalls||[],this.getLanguagesCalls.push(e),1<this.getLanguagesCalls.length||this.loadUrl({},t,function(t,o,e){var i,s;!r.somethingLoaded&&e&&e.resourceNotExisting?(r.isProjectNotExisting=!0,i="locize project ".concat(r.options.projectId," does not exist!"),r.isProjectNotExistingErrorMessage=i,s="standard"===r.options.cdnType?"pro":"standard",e=p(s),e=(0,l.interpolate)(e.getLanguagesPath,{projectId:r.options.projectId}),r.loadUrl({},e,function(e,t,o){e||!t||o&&o.resourceNotExisting?!r.somethingLoaded&&o&&o.resourceNotExisting&&(r.isProjectNotExisting=!0,r.storage.setProjectNotExisting(r.options.projectId)):(i+=" It seems you're using the wrong cdnType. Your locize project is configured to use \"".concat(s,'" but here you\'ve configured "').concat(r.options.cdnType,'".'),r.isProjectNotExistingErrorMessage=i);var n=new Error(i),e=r.getLanguagesCalls;r.getLanguagesCalls=[],e.forEach(function(e){return e(n)})})):(o&&(r.loadedLanguages=Object.keys(o),e=r.loadedLanguages.reduce(function(e,t){return e=o[t].isReferenceLanguage?t:e},""))&&r.options.referenceLng!==e&&(r.options.referenceLng=e),r.somethingLoaded=!0,e=r.getLanguagesCalls,r.getLanguagesCalls=[],e.forEach(function(e){return e(t,o)}))}))),o}},{key:"getOptions",value:function(o){var n,i=this;return o||(n=(0,l.defer)(),o=function(e,t){if(e)return n.reject(e);n.resolve(t)}),this.getLanguages(function(e,n){var t;return e?o(e):(e=Object.keys(n)).length?(t=e.reduce(function(e,t){var o=n[t];return o.translated[i.options.version]&&o.translated[i.options.version]>=i.options.translatedPercentageThreshold&&e.push(t),e},[]),e=e.reduce(function(e,t){return-1<t.indexOf("-")||e},!1),void o(null,{fallbackLng:i.options.referenceLng,referenceLng:i.options.referenceLng,supportedLngs:0===t.length&&i.options.referenceLng?[i.options.referenceLng]:t,load:e?"all":"languageOnly"},n)):o(new Error("was unable to load languages via API"))}),n}},{key:"checkIfProjectExists",value:function(t){var e=this,o=(this.services||{logger:console}).logger;this.somethingLoaded?t&&t(null):this.alreadyRequestedCheckIfProjectExists?setTimeout(function(){return e.checkIfProjectExists(t)},c(this.options.checkForProjectTimeout)):(this.alreadyRequestedCheckIfProjectExists=!0,this.getLanguages(function(e){e&&e.message&&0<e.message.indexOf("does not exist")&&o&&o.error(e.message),t&&t(e)}))}},{key:"checkIfLanguagesLoaded",value:function(t){var o=(this.services||{logger:console}).logger;this.loadedLanguages?t&&t(null):this.getLanguages(function(e){e&&e.message&&0<e.message.indexOf("does not exist")&&o&&o.error(e.message),t&&t(e)})}},{key:"read",value:function(n,e,i){var s=this,r=(this.services||{logger:console}).logger,t={};if(this.options.private){var o=(0,l.isMissingOption)(this.options,["projectId","version","apiKey"]);if(o)return i(new Error(o),!1);o=(0,l.interpolate)(this.options.privatePath,{lng:n,ns:e,projectId:this.options.projectId,version:this.options.version}),t={authorize:!0}}else{var a=(0,l.isMissingOption)(this.options,["projectId","version"]);if(a)return i(new Error(a),!1);o=(0,l.interpolate)(this.options.loadPath,{lng:n,ns:e,projectId:this.options.projectId,version:this.options.version})}!this.isProjectNotExisting&&this.storage.isProjectNotExisting(this.options.projectId)&&(this.isProjectNotExisting=!0),this.isProjectNotExisting?(a=new Error(this.isProjectNotExistingErrorMessage||"locize project ".concat(this.options.projectId," does not exist!")),r&&r.error(a.message),i&&i(a)):this.warnedLanguages&&-1<this.warnedLanguages.indexOf(n)?(e=new Error('Will not continue to load language "'.concat(n,'" since it is not available in locize project ').concat(this.options.projectId,"!")),r&&r.error(e.message),i&&i(e)):this.loadUrl(t,o,function(e,t,o){o=o&&o.resourceNotExisting;o||(s.hasResourcesForLng||(s.hasResourcesForLng={}),s.hasResourcesForLng[n]=!0),!o||s.hasResourcesForLng&&s.hasResourcesForLng[n]||setTimeout(function(){s.checkIfLanguagesLoaded(function(){!s.loadedLanguages||-1<s.loadedLanguages.indexOf(n)||s.warnedLanguages&&-1<s.warnedLanguages.indexOf(n)||(s.warnedLanguages||(s.warnedLanguages=[]),s.warnedLanguages.push(n),r&&r.error('Language "'.concat(n,'" is not available in locize project ').concat(s.options.projectId,"!")))})},c(s.options.checkForProjectTimeout)),s.somethingLoaded||(o?setTimeout(function(){return s.checkIfProjectExists()},c(s.options.checkForProjectTimeout)):s.somethingLoaded=!0),i(e,t)})}},{key:"loadUrl",value:function(e,r,t,a){function o(e,t){var o,n,i=t&&t.resourceNotExisting;if(t&&(408===t.status||400===t.status))return a("failed loading "+r,!0,{resourceNotExisting:i});if(t&&(500<=t.status&&t.status<600||!t.status))return a("failed loading "+r,!0,{resourceNotExisting:i});if(t&&400<=t.status&&t.status<500)return a("failed loading "+r,!1,{resourceNotExisting:i});if(!t&&e&&e.message){var s=e.message.toLowerCase();if(["failed","fetch","network","load"].find(function(e){return-1<s.indexOf(e)}))return a("failed loading "+r+": "+e.message,!0,{resourceNotExisting:i})}if(e)return a(e,!1);try{o="string"==typeof t.data?JSON.parse(t.data):t.data}catch(e){n="failed parsing "+r+" to json"}return n?a(n,!1):c.options.failLoadingOnEmptyJSON&&!Object.keys(o).length?a("loaded result empty for "+r,!1,{resourceNotExisting:i}):void a(null,o,{resourceNotExisting:i})}var c=this;e=(0,l.defaults)(e,this.options),"function"==typeof t&&(a=t,t=void 0),a=a||function(){};if(!this.options.request||0<r.indexOf("/languages/".concat(e.projectId)))return(0,f.default)(e,r,t,o);e=h(r,e,t);v(this.options,e,o)}},{key:"create",value:function(t,o,n,i,s,r){var a=this;"function"!=typeof s&&(s=function(){}),this.checkIfProjectExists(function(e){return e?s(e):(e=(0,l.isMissingOption)(a.options,["projectId","version","apiKey","referenceLng"]))?s(new Error(e)):a.isAddOrUpdateAllowed?((t="string"==typeof t?[t]:t).filter(function(e){return e===a.options.referenceLng}).length<1&&a.services&&a.services.logger&&a.services.logger.warn('locize-backend: will not save missings because the reference language "'.concat(a.options.referenceLng,'" was not in the list of to save languages: ').concat(t.join(", ")," (open your site in the reference language to save missings).")),void t.forEach(function(e){e===a.options.referenceLng&&a.queue.call(a,a.options.referenceLng,o,n,i,s,r)})):s("host is not allowed to create key.")})}},{key:"update",value:function(t,o,n,i,s,r){var a=this;"function"!=typeof s&&(s=function(){}),this.checkIfProjectExists(function(e){return e?s(e):(e=(0,l.isMissingOption)(a.options,["projectId","version","apiKey","referenceLng"]))?s(new Error(e)):a.isAddOrUpdateAllowed?("string"==typeof t&&(t=[t]),(r=r||{}).isUpdate=!0,void t.forEach(function(e){e===a.options.referenceLng&&a.queue.call(a,a.options.referenceLng,o,n,i,s,r)})):s("host is not allowed to update key.")})}},{key:"writePage",value:function(e,t,o,n){function i(e){--d||n(e)}var s=(0,l.interpolate)(this.options.addPath,{lng:e,ns:t,projectId:this.options.projectId,version:this.options.version}),e=(0,l.interpolate)(this.options.updatePath,{lng:e,ns:t,projectId:this.options.projectId,version:this.options.version}),r=!1,a=!1,c={},u={},d=(o.forEach(function(e){var t=e.options&&e.options.tDescription?{value:e.fallbackValue||"",context:{text:e.options.tDescription}}:e.fallbackValue||"";e.options&&e.options.isUpdate?(a=a||!0,u[e.key]=t):(r=r||!0,c[e.key]=t)}),0);r&&d++,a&&d++;d||i(),r&&(this.options.request?(t=h(s,(0,l.defaults)({authorize:!0},this.options),c),v(this.options,t,i)):(0,f.default)((0,l.defaults)({authorize:!0},this.options),s,c,i)),a&&(this.options.request?(o=h(e,(0,l.defaults)({authorize:!0},this.options),u),v(this.options,o,i)):(0,f.default)((0,l.defaults)({authorize:!0},this.options),e,u,i))}},{key:"write",value:function(e,t){var o=this,n=(0,l.getPath)(this.queuedWrites,["locks",e,t]);if(!n){var i=(0,l.getPath)(this.queuedWrites,[e,t]),s=((0,l.setPath)(this.queuedWrites,[e,t],[]),i.filter(function(e){return e.callback}).map(function(e){return e.callback}));if(i.length){(0,l.setPath)(this.queuedWrites,["locks",e,t],!0);var r=function(){(0,l.setPath)(o.queuedWrites,["locks",e,t],!1),s.forEach(function(e){return e()}),o.options.onSaved&&o.options.onSaved(e,t),o.debouncedProcess(e,t)},a=i.length/1e3,c=0,u=i.splice(0,1e3);for(this.writePage(e,t,u,function(){a<=++c&&r()});1e3===u.length;)(u=i.splice(0,1e3)).length&&this.writePage(e,t,u,function(){a<=++c&&r()})}}}},{key:"process",value:function(){var o=this;Object.keys(this.queuedWrites).forEach(function(t){"locks"!==t&&Object.keys(o.queuedWrites[t]).forEach(function(e){o.queuedWrites[t][e].length&&o.write(t,e)})})}},{key:"queue",value:function(e,t,o,n,i,s){(0,l.pushPath)(this.queuedWrites,[e,t],{key:o,fallbackValue:n||"",callback:i,options:s}),this.debouncedProcess()}}])&&n(e.prototype,i),s&&n(e,s),Object.defineProperty(e,"prototype",{writable:!1});var s=e;s.type="backend",o.default=s;t.exports=o.default},{"./request.js":2,"./utils.js":3}],2:[function(e,t,o){!function(r){!function(){function d(e){return(d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(o,"__esModule",{value:!0}),o.default=void 0;var l,f,p="function"==typeof fetch?fetch:void 0;if(void 0!==r&&r.fetch?p=r.fetch:"undefined"!=typeof window&&window.fetch&&(p=window.fetch),"function"!=typeof XMLHttpRequest&&"object"!==("undefined"==typeof XMLHttpRequest?"undefined":d(XMLHttpRequest))||(void 0!==r&&r.XMLHttpRequest?l=r.XMLHttpRequest:"undefined"!=typeof window&&window.XMLHttpRequest&&(l=window.XMLHttpRequest)),"function"==typeof ActiveXObject&&(void 0!==r&&r.ActiveXObject?f=r.ActiveXObject:"undefined"!=typeof window&&window.ActiveXObject&&(f=window.ActiveXObject)),!(p="function"!=typeof p?void 0:p)&&!l&&!f)try{p=e("cross-fetch")}catch(e){}function g(e,t,o,i){function n(t){var o,n=t.headers&&"Error from cloudfront"===t.headers.get("x-cache");return"standard"!==e.cdnType||404!==t.status||t.headers&&t.headers.get("x-cache")?t.ok?(o=t.headers&&t.headers.get("cache-control"),void t.text().then(function(e){i(null,{status:t.status,data:e,resourceNotExisting:n,cacheControl:o})}).catch(i)):i(t.statusText||"Error",{status:t.status,resourceNotExisting:n}):i(null,{status:200,data:"{}",resourceNotExisting:n=!0})}var s={};"undefined"==typeof window&&void 0!==r&&void 0!==r.process&&r.process.versions&&r.process.versions.node&&(s["User-Agent"]="i18next-locize-backend (node/".concat(r.process.version,"; ").concat(r.process.platform," ").concat(r.process.arch,")")),e.authorize&&e.apiKey&&(s.Authorization=e.apiKey),(o||e.setContentTypeJSON)&&(s["Content-Type"]="application/json"),("function"==typeof fetch?fetch:p)(t,{method:o?"POST":"GET",body:o?JSON.stringify(o):void 0,headers:s}).then(n).catch(i)}var h={};o.default=function(e,n,i,t){"function"==typeof i&&(t=i,i=void 0),t=t||function(){};var s="undefined"==typeof window&&e.useCacheLayer;if(s&&!i&&!e.noCache&&h[n]&&h[n].expires>Date.now())return t(null,h[n].data);var r=t;if(t=function(e,t){var o;s&&!e&&t&&!i&&t.cacheControl&&0<(o=(o=(o=t.cacheControl)&&o.match(/max-age=([0-9]+)/))?parseInt(o[1],10):0)&&(h[n]={data:t,expires:Date.now()+1e3*o}),r(e,t)},!i&&e.noCache&&"standard"===e.cdnType&&(n+=(0<=n.indexOf("?")?"&":"?")+"cache=no"),p)return g(e,n,i,t);if("function"==typeof XMLHttpRequest||"object"===("undefined"==typeof XMLHttpRequest?"undefined":d(XMLHttpRequest))||"function"==typeof ActiveXObject){var o=e,e=n,a=i,c=t;try{var u=l?new l:new f("MSXML2.XMLHTTP.3.0");u.open(a?"POST":"GET",e,1),o.crossDomain||u.setRequestHeader("X-Requested-With","XMLHttpRequest"),o.authorize&&o.apiKey&&u.setRequestHeader("Authorization",o.apiKey),(a||o.setContentTypeJSON)&&u.setRequestHeader("Content-Type","application/json"),u.onreadystatechange=function(){var e="Error from cloudfront"===u.getResponseHeader("x-cache");if("standard"===o.cdnType&&404===u.status&&!u.getResponseHeader("x-cache"))return e=!0,3<u.readyState&&c(null,{status:200,data:"{}",resourceNotExisting:e});var t=u.getResponseHeader("Cache-Control");3<u.readyState&&c(400<=u.status?u.statusText:null,{status:u.status,data:u.responseText,resourceNotExisting:e,cacheControl:t})},u.send(JSON.stringify(a))}catch(e){console&&console.log(e)}}else t(new Error("No fetch and no xhr implementation found!"))};t.exports=o.default}.call(this)}.call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"cross-fetch":4}],3:[function(e,t,o){Object.defineProperty(o,"__esModule",{value:!0}),o.debounce=function(n,i,s){var r;return function(){var e=this,t=arguments,o=s&&!r;clearTimeout(r),r=setTimeout(function(){r=null,s||n.apply(e,t)},i),o&&n.apply(e,t)}},o.defaults=function(o){return n.call(i.call(arguments,1),function(e){if(e)for(var t in e)void 0===o[t]&&(o[t]=e[t])}),o},o.defer=function(){var o,n,e=new Promise(function(e,t){o=e,n=t});return e.resolve=o,e.reject=n,e},o.getPath=function(e,t){e=s(e,t),t=e.obj,e=e.k;if(t)return t[e]},o.interpolate=function(e,t,o){var n,i;for(;n=r.exec(e);)i=(i=(i="string"!=typeof(i=n[1].trim())?(e=>null==e?"":""+e)(i):i)||"").replace(/\$/g,"$$$$"),e=e.replace(n[0],t[i]||i),r.lastIndex=0;return e},o.isMissingOption=a,o.optionExist=function(e,t){return!a(e,t)},o.pushPath=function(e,t,o,n){e=s(e,t,Object),t=e.obj,e=e.k;t[e]=t[e]||[],n&&(t[e]=t[e].concat(o));n||t[e].push(o)},o.setPath=function(e,t,o){e=s(e,t,Object);e.obj[e.k]=o};var o=[],n=o.forEach,i=o.slice;function s(e,t,o){function n(e){return e&&-1<e.indexOf("###")?e.replace(/###/g,"."):e}for(var i="string"!=typeof t?[].concat(t):t.split(".");1<i.length;){if(!e)return{};var s=n(i.shift());!e[s]&&o&&(e[s]=new o),e=e[s]}return e?{obj:e,k:n(i.shift())}:{}}var r=new RegExp("{{(.+?)}}","g");function a(o,e){return e.reduce(function(e,t){return e||(!o||!o[t]||"string"!=typeof o[t]||!o[t].toLowerCase()===t.toLowerCase())&&(e='i18next-locize-backend :: got "'.concat(o[t],'" in options for ').concat(t," which is invalid."),console.warn(e),e)},!1)}},{}],4:[function(e,t,o){},{}]},{},[1])(1)});
    \ No newline at end of file
    +!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).i18nextLocizeBackend=e()}(function(){return function o(i,r,s){function a(t,e){if(!r[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(e=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",e}n=r[t]={exports:{}},i[t][0].call(n.exports,function(e){return a(i[t][1][e]||e)},n,n.exports,o,i,r,s)}return r[t].exports}for(var c="function"==typeof require&&require,e=0;e<s.length;e++)a(s[e]);return a}({1:[function(e,t,n){Object.defineProperty(n,"__esModule",{value:!0}),n.default=void 0;var d=e("./utils.js"),f=(e=e("./request.js"))&&e.__esModule?e:{default:e};function l(e){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function o(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,(e=>(e=((e,t)=>{if("object"!=l(e)||!e)return e;var n=e[Symbol.toPrimitive];if(void 0===n)return("string"===t?String:Number)(e);if("object"!=l(n=n.call(e,t||"default")))return n;throw new TypeError("@@toPrimitive must return a primitive value.")})(e,"string"),"symbol"==l(e)?e:e+""))(o.key),o)}}function p(e){return{loadPath:"https://api".concat("standard"===(e=e||"standard")?".lite":"",".locize.app/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),privatePath:"https://api".concat("standard"===e?".lite":"",".locize.app/private/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),getLanguagesPath:"https://api".concat("standard"===e?".lite":"",".locize.app/languages/{{projectId}}"),addPath:"https://api".concat("standard"===e?".lite":"",".locize.app/missing/{{projectId}}/{{version}}/{{lng}}/{{ns}}"),updatePath:"https://api".concat("standard"===e?".lite":"",".locize.app/update/{{projectId}}/{{version}}/{{lng}}/{{ns}}")}}try{var g="undefined"!=typeof window&&null!==window.localStorage,i="notExistingLocizeProject";window.localStorage.setItem(i,"foo"),window.localStorage.removeItem(i)}catch(e){g=!1}function h(e,t,n){var o={};return t.authorize&&t.apiKey&&(o.Authorization=t.apiKey),(n||t.setContentTypeJSON)&&(o["Content-Type"]="application/json"),{method:n?"POST":"GET",url:e,headers:o,body:n}}function v(e,t,n){if(1===e.request.length)try{var o=e.request(t);o&&"function"==typeof o.then?o.then(function(e){return n(null,e)}).catch(n):n(null,o)}catch(e){n(e)}else e.request(t,n)}function c(e){var t=.25*e,n=Math.max(0,e-t),e=e+t;return Math.floor(n+Math.random()*(e-n))}e=function e(t){var n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{},i=3<arguments.length?arguments[3]:void 0,r=this,s=e;if(!(r instanceof s))throw new TypeError("Cannot call a class as a function");this.services=t,this.options=n,this.allOptions=o,this.type="backend",t&&t.projectId?this.init(null,t,o,n):this.init(t,n,o,i)},(i=[{key:"init",value:function(e){var n,o=this,i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},t=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{},r=3<arguments.length?arguments[3]:void 0,s=(!i.referenceLng&&t.fallbackLng&&Array.isArray(t.fallbackLng)&&"dev"!==t.fallbackLng[0]&&(i.referenceLng=t.fallbackLng[0]),this.services=e,(0,d.defaults)({},i)),a=(0,d.defaults)(i,this.options||{}),c=(c=a.cdnType,(0,d.defaults)({cdnType:c=c||"standard",noCache:!1,referenceLng:"en",crossDomain:!0,setContentTypeJSON:!1,version:"latest",private:!1,translatedPercentageThreshold:.9,failLoadingOnEmptyJSON:!1,allowedAddOrUpdateHosts:["localhost"],onSaved:!1,reloadInterval:"undefined"==typeof window&&36e5,checkForProjectTimeout:3e3,storageExpiration:36e5,writeDebounce:5e3,useCacheLayer:"undefined"==typeof window},p(c))),u=(a.reloadInterval&&a.reloadInterval<3e5&&(console.warn("Your configured reloadInterval option is to low."),a.reloadInterval=c.reloadInterval),this.options=(0,d.defaults)(i,this.options||{},c),this.allOptions=t,this.somethingLoaded=!1,this.isProjectNotExisting=!1,this.storage=(n=this.options.storageExpiration,a=function(){},c=function(){},g?(a=function(e){window.localStorage.setItem("notExistingLocizeProject_".concat(e),Date.now())},c=function(e){var t=window.localStorage.getItem("notExistingLocizeProject_".concat(e));return!(!t||Date.now()-t>n&&(window.localStorage.removeItem("notExistingLocizeProject_".concat(e)),1))}):"undefined"!=typeof document&&(a=function(e){var t=new Date,t=(t.setTime(t.getTime()+n),"; expires=".concat(t.toGMTString())),e="notExistingLocizeProject_".concat(e);try{document.cookie="".concat(e,"=").concat(Date.now()).concat(t,";path=/")}catch(e){}},c=function(e){var e="notExistingLocizeProject_".concat(e),t="".concat(e,"=");try{for(var n=document.cookie.split(";"),o=0;o<n.length;o++){for(var i=n[o];" "===i.charAt(0);)i=i.substring(1,i.length);if(0===i.indexOf(t))return!0}}catch(e){}return!1}),{setProjectNotExisting:a,isProjectNotExisting:c}),p(this.options.cdnType)),a=(Object.keys(u).forEach(function(e){s[e]||(o.options[e]=u[e])}),t.debug&&void 0===s.noCache&&"standard"===this.options.cdnType&&(this.options.noCache=!0),this.options.noCache&&"standard"!==this.options.cdnType&&console.warn("The 'noCache' option is not available for 'cdnType' '".concat(this.options.cdnType,"'!")),"undefined"!=typeof window&&window.location&&window.location.hostname);a?(this.isAddOrUpdateAllowed="function"==typeof this.options.allowedAddOrUpdateHosts?this.options.allowedAddOrUpdateHosts(a):-1<this.options.allowedAddOrUpdateHosts.indexOf(a),e&&e.logger&&(t.saveMissing||t.updateMissing)&&(this.isAddOrUpdateAllowed?"localhost"!==a&&e.logger.warn('locize-backend: you are using the save or update missings feature from this host "'.concat(a,'".\nMake sure you will not use it in production!\nhttps://www.locize.com/docs/going-to-production')):e.logger.warn("function"==typeof this.options.allowedAddOrUpdateHosts?'locize-backend: will not save or update missings because allowedAddOrUpdateHosts returned false for the host "'.concat(a,'".'):'locize-backend: will not save or update missings because the host "'.concat(a,'" was not in the list of allowedAddOrUpdateHosts: ').concat(this.options.allowedAddOrUpdateHosts.join(", ")," (matches need to be exact).")))):this.isAddOrUpdateAllowed=!0,"function"==typeof r&&this.getOptions(function(e,t,n){if(e)return r(e);o.options.referenceLng=i.referenceLng||t.referenceLng||o.options.referenceLng,r(null,t,n)}),this.queuedWrites={pending:{}},this.debouncedProcess=(0,d.debounce)(this.process,this.options.writeDebounce),this.interval&&clearInterval(this.interval),this.options.reloadInterval&&this.options.projectId&&(this.interval=setInterval(function(){return o.reload()},this.options.reloadInterval),"object"===l(this.interval))&&"function"==typeof this.interval.unref&&this.interval.unref()}},{key:"reload",value:function(){var t,e,n=this,o=this.services||{logger:console},i=o.backendConnector,r=o.languageUtils,s=o.logger;!i||(o=i.language)&&"cimode"===o.toLowerCase()||(t=[],(e=function(e){r.toResolveHierarchy(e).forEach(function(e){t.indexOf(e)<0&&t.push(e)})})(o),this.allOptions.preload&&this.allOptions.preload.forEach(e),t.forEach(function(o){n.allOptions.ns.forEach(function(n){i.read(o,n,"read",null,null,function(e,t){e&&s.warn("loading namespace ".concat(n," for language ").concat(o," failed"),e),!e&&t&&s.log("loaded namespace ".concat(n," for language ").concat(o),t),i.loaded("".concat(o,"|").concat(n),e,t)})})}))}},{key:"getLanguages",value:function(e){var n,s=this,t=(e||(n=(0,d.defer)(),e=function(e,t){if(e)return n.reject(e);n.resolve(t)}),(0,d.isMissingOption)(this.options,["projectId"]));return t?e(new Error(t)):null==(t=(0,d.interpolateUrl)(this.options.getLanguagesPath,{projectId:this.options.projectId}))?e(new Error("i18next-locize-backend: unsafe projectId — refusing to build request URL for projectId="+(0,d.sanitizeLogValue)(String(this.options.projectId)))):(!this.isProjectNotExisting&&this.storage.isProjectNotExisting(this.options.projectId)&&(this.isProjectNotExisting=!0),this.isProjectNotExisting?e(new Error(this.isProjectNotExistingErrorMessage||"Locize project ".concat(this.options.projectId," does not exist!"))):(this.getLanguagesCalls=this.getLanguagesCalls||[],this.getLanguagesCalls.push(e),1<this.getLanguagesCalls.length||this.loadUrl({},t,function(t,n,e){var i,r;if(!s.somethingLoaded&&e&&e.resourceNotExisting)return s.isProjectNotExisting=!0,i="Locize project ".concat(s.options.projectId," does not exist!"),s.isProjectNotExistingErrorMessage=i,r="standard"===s.options.cdnType?"pro":"standard",e=p(r),null==(e=(0,d.interpolateUrl)(e.getLanguagesPath,{projectId:s.options.projectId}))?void 0:void s.loadUrl({},e,function(e,t,n){e||!t||n&&n.resourceNotExisting?!s.somethingLoaded&&n&&n.resourceNotExisting&&(s.isProjectNotExisting=!0,s.storage.setProjectNotExisting(s.options.projectId)):(i+=" It seems you're using the wrong cdnType. Your Locize project is configured to use \"".concat(r,'" but here you\'ve configured "').concat(s.options.cdnType,'".'),s.isProjectNotExistingErrorMessage=i);var o=new Error(i),e=s.getLanguagesCalls;s.getLanguagesCalls=[],e.forEach(function(e){return e(o)})});n&&(s.loadedLanguages=Object.keys(n),e=s.loadedLanguages.reduce(function(e,t){return e=n[t].isReferenceLanguage?t:e},""))&&s.options.referenceLng!==e&&(s.options.referenceLng=e),s.somethingLoaded=!0;e=s.getLanguagesCalls;s.getLanguagesCalls=[],e.forEach(function(e){return e(t,n)})}))),n}},{key:"getOptions",value:function(n){var o,i=this;return n||(o=(0,d.defer)(),n=function(e,t){if(e)return o.reject(e);o.resolve(t)}),this.getLanguages(function(e,o){var t;return e?n(e):(e=Object.keys(o)).length?(t=e.reduce(function(e,t){var n=o[t];return n.translated[i.options.version]&&n.translated[i.options.version]>=i.options.translatedPercentageThreshold&&e.push(t),e},[]),e=e.reduce(function(e,t){return-1<t.indexOf("-")||e},!1),void n(null,{fallbackLng:i.options.referenceLng,referenceLng:i.options.referenceLng,supportedLngs:0===t.length&&i.options.referenceLng?[i.options.referenceLng]:t,load:e?"all":"languageOnly"},o)):n(new Error("was unable to load languages via API"))}),o}},{key:"checkIfProjectExists",value:function(t){var e=this,n=(this.services||{logger:console}).logger;this.somethingLoaded?t&&t(null):this.alreadyRequestedCheckIfProjectExists?setTimeout(function(){return e.checkIfProjectExists(t)},c(this.options.checkForProjectTimeout)):(this.alreadyRequestedCheckIfProjectExists=!0,this.getLanguages(function(e){e&&e.message&&0<e.message.indexOf("does not exist")&&n&&n.error(e.message),t&&t(e)}))}},{key:"checkIfLanguagesLoaded",value:function(t){var n=(this.services||{logger:console}).logger;this.loadedLanguages?t&&t(null):this.getLanguages(function(e){e&&e.message&&0<e.message.indexOf("does not exist")&&n&&n.error(e.message),t&&t(e)})}},{key:"read",value:function(o,e,i){var r=this,s=(this.services||{logger:console}).logger,t={};if(this.options.private){var n=(0,d.isMissingOption)(this.options,["projectId","version","apiKey"]);if(n)return i(new Error(n),!1);n=(0,d.interpolateUrl)(this.options.privatePath,{lng:o,ns:e,projectId:this.options.projectId,version:this.options.version}),t={authorize:!0}}else{var a=(0,d.isMissingOption)(this.options,["projectId","version"]);if(a)return i(new Error(a),!1);n=(0,d.interpolateUrl)(this.options.loadPath,{lng:o,ns:e,projectId:this.options.projectId,version:this.options.version})}if(null==n)return i(new Error("i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to build request URL for lng="+(0,d.sanitizeLogValue)(String(o))+" ns="+(0,d.sanitizeLogValue)(String(e))),!1);!this.isProjectNotExisting&&this.storage.isProjectNotExisting(this.options.projectId)&&(this.isProjectNotExisting=!0),this.isProjectNotExisting?(a=new Error(this.isProjectNotExistingErrorMessage||"Locize project ".concat(this.options.projectId," does not exist!")),s&&s.error(a.message),i&&i(a)):this.warnedLanguages&&-1<this.warnedLanguages.indexOf(o)?(e=new Error('Will not continue to load language "'.concat(o,'" since it is not available in Locize project ').concat(this.options.projectId,"!")),s&&s.error(e.message),i&&i(e)):this.loadUrl(t,n,function(e,t,n){n=n&&n.resourceNotExisting;n||(r.hasResourcesForLng||(r.hasResourcesForLng={}),r.hasResourcesForLng[o]=!0),!n||r.hasResourcesForLng&&r.hasResourcesForLng[o]||setTimeout(function(){r.checkIfLanguagesLoaded(function(){!r.loadedLanguages||-1<r.loadedLanguages.indexOf(o)||r.warnedLanguages&&-1<r.warnedLanguages.indexOf(o)||(r.warnedLanguages||(r.warnedLanguages=[]),r.warnedLanguages.push(o),s&&s.error('Language "'.concat(o,'" is not available in Locize project ').concat(r.options.projectId,"!")))})},c(r.options.checkForProjectTimeout)),r.somethingLoaded||(n?setTimeout(function(){return r.checkIfProjectExists()},c(r.options.checkForProjectTimeout)):r.somethingLoaded=!0),i(e,t)})}},{key:"loadUrl",value:function(e,s,t,a){function n(e,t){var n,o,i=t&&t.resourceNotExisting;if(t&&(408===t.status||400===t.status))return a("failed loading "+s,!0,{resourceNotExisting:i});if(t&&(500<=t.status&&t.status<600||!t.status))return a("failed loading "+s,!0,{resourceNotExisting:i});if(t&&400<=t.status&&t.status<500)return a("failed loading "+s,!1,{resourceNotExisting:i});if(!t&&e&&e.message){var r=e.message.toLowerCase();if(["failed","fetch","network","load"].find(function(e){return-1<r.indexOf(e)}))return a("failed loading "+s+": "+e.message,!0,{resourceNotExisting:i})}if(e)return a(e,!1);try{n="string"==typeof t.data?JSON.parse(t.data):t.data}catch(e){o="failed parsing "+s+" to json"}return o?a(o,!1):c.options.failLoadingOnEmptyJSON&&!Object.keys(n).length?a("loaded result empty for "+s,!1,{resourceNotExisting:i}):void a(null,n,{resourceNotExisting:i})}var c=this;e=(0,d.defaults)(e,this.options),"function"==typeof t&&(a=t,t=void 0),a=a||function(){};if(!this.options.request||0<s.indexOf("/languages/".concat(e.projectId)))return(0,f.default)(e,s,t,n);e=h(s,e,t);v(this.options,e,n)}},{key:"create",value:function(t,n,o,i,r,s){var a=this;"function"!=typeof r&&(r=function(){}),this.checkIfProjectExists(function(e){return e?r(e):(e=(0,d.isMissingOption)(a.options,["projectId","version","apiKey","referenceLng"]))?r(new Error(e)):a.isAddOrUpdateAllowed?((t="string"==typeof t?[t]:t).filter(function(e){return e===a.options.referenceLng}).length<1&&a.services&&a.services.logger&&a.services.logger.warn('locize-backend: will not save missings because the reference language "'.concat(a.options.referenceLng,'" was not in the list of to save languages: ').concat(t.join(", ")," (open your site in the reference language to save missings).")),void t.forEach(function(e){e===a.options.referenceLng&&a.queue.call(a,a.options.referenceLng,n,o,i,r,s)})):r("host is not allowed to create key.")})}},{key:"update",value:function(t,n,o,i,r,s){var a=this;"function"!=typeof r&&(r=function(){}),this.checkIfProjectExists(function(e){return e?r(e):(e=(0,d.isMissingOption)(a.options,["projectId","version","apiKey","referenceLng"]))?r(new Error(e)):a.isAddOrUpdateAllowed?("string"==typeof t&&(t=[t]),(s=s||{}).isUpdate=!0,void t.forEach(function(e){e===a.options.referenceLng&&a.queue.call(a,a.options.referenceLng,n,o,i,r,s)})):r("host is not allowed to update key.")})}},{key:"writePage",value:function(e,t,n,o){var i,r,s,a,c,u=(0,d.interpolateUrl)(this.options.addPath,{lng:e,ns:t,projectId:this.options.projectId,version:this.options.version}),l=(0,d.interpolateUrl)(this.options.updatePath,{lng:e,ns:t,projectId:this.options.projectId,version:this.options.version});null==u||null==l?"function"==typeof o&&o(new Error("i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to persist missing keys for lng="+(0,d.sanitizeLogValue)(String(e))+" ns="+(0,d.sanitizeLogValue)(String(t)))):(r=i=!1,s={},a={},n.forEach(function(e){var t=e.options&&e.options.tDescription?{value:e.fallbackValue||"",context:{text:e.options.tDescription}}:e.fallbackValue||"";e.options&&e.options.isUpdate?(r=r||!0,a[e.key]=t):(i=i||!0,s[e.key]=t)}),c=0,i&&c++,r&&c++,e=function(e){--c||o(e)},c||e(),i&&(this.options.request?(t=h(u,(0,d.defaults)({authorize:!0},this.options),s),v(this.options,t,e)):(0,f.default)((0,d.defaults)({authorize:!0},this.options),u,s,e)),r&&(this.options.request?(n=h(l,(0,d.defaults)({authorize:!0},this.options),a),v(this.options,n,e)):(0,f.default)((0,d.defaults)({authorize:!0},this.options),l,a,e)))}},{key:"write",value:function(e,t){var n=this,o=(0,d.getPath)(this.queuedWrites,["locks",e,t]);if(!o){var i=(0,d.getPath)(this.queuedWrites,[e,t]),r=((0,d.setPath)(this.queuedWrites,[e,t],[]),i.filter(function(e){return e.callback}).map(function(e){return e.callback}));if(i.length){(0,d.setPath)(this.queuedWrites,["locks",e,t],!0);var s=function(){(0,d.setPath)(n.queuedWrites,["locks",e,t],!1),r.forEach(function(e){return e()}),n.options.onSaved&&n.options.onSaved(e,t),n.debouncedProcess(e,t)},a=i.length/1e3,c=0,u=i.splice(0,1e3);for(this.writePage(e,t,u,function(){a<=++c&&s()});1e3===u.length;)(u=i.splice(0,1e3)).length&&this.writePage(e,t,u,function(){a<=++c&&s()})}}}},{key:"process",value:function(){var n=this;Object.keys(this.queuedWrites).forEach(function(t){"locks"!==t&&Object.keys(n.queuedWrites[t]).forEach(function(e){n.queuedWrites[t][e].length&&n.write(t,e)})})}},{key:"queue",value:function(e,t,n,o,i,r){(0,d.pushPath)(this.queuedWrites,[e,t],{key:n,fallbackValue:o||"",callback:i,options:r}),this.debouncedProcess()}}])&&o(e.prototype,i),r&&o(e,r),Object.defineProperty(e,"prototype",{writable:!1});var r=e;r.type="backend",n.default=r;t.exports=n.default},{"./request.js":2,"./utils.js":3}],2:[function(e,t,n){!function(s){!function(){function l(e){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(n,"__esModule",{value:!0}),n.default=void 0;var d,f,p="function"==typeof fetch?fetch:void 0;if(void 0!==s&&s.fetch?p=s.fetch:"undefined"!=typeof window&&window.fetch&&(p=window.fetch),"function"!=typeof XMLHttpRequest&&"object"!==("undefined"==typeof XMLHttpRequest?"undefined":l(XMLHttpRequest))||(void 0!==s&&s.XMLHttpRequest?d=s.XMLHttpRequest:"undefined"!=typeof window&&window.XMLHttpRequest&&(d=window.XMLHttpRequest)),"function"==typeof ActiveXObject&&(void 0!==s&&s.ActiveXObject?f=s.ActiveXObject:"undefined"!=typeof window&&window.ActiveXObject&&(f=window.ActiveXObject)),!(p="function"!=typeof p?void 0:p)&&!d&&!f)try{p=e("cross-fetch")}catch(e){}function g(e,t,n,i){function o(t){var n,o=t.headers&&"Error from cloudfront"===t.headers.get("x-cache");return"standard"!==e.cdnType||404!==t.status||t.headers&&t.headers.get("x-cache")?t.ok?(n=t.headers&&t.headers.get("cache-control"),void t.text().then(function(e){i(null,{status:t.status,data:e,resourceNotExisting:o,cacheControl:n})}).catch(i)):i(t.statusText||"Error",{status:t.status,resourceNotExisting:o}):i(null,{status:200,data:"{}",resourceNotExisting:o=!0})}var r={};"undefined"==typeof window&&void 0!==s&&void 0!==s.process&&s.process.versions&&s.process.versions.node&&(r["User-Agent"]="i18next-locize-backend (node/".concat(s.process.version,"; ").concat(s.process.platform," ").concat(s.process.arch,")")),e.authorize&&e.apiKey&&(r.Authorization=e.apiKey),(n||e.setContentTypeJSON)&&(r["Content-Type"]="application/json"),("function"==typeof fetch?fetch:p)(t,{method:n?"POST":"GET",body:n?JSON.stringify(n):void 0,headers:r}).then(o).catch(i)}var h={};n.default=function(e,o,i,t){"function"==typeof i&&(t=i,i=void 0),t=t||function(){};var r="undefined"==typeof window&&e.useCacheLayer;if(r&&!i&&!e.noCache&&h[o]&&h[o].expires>Date.now())return t(null,h[o].data);var s=t;if(t=function(e,t){var n;r&&!e&&t&&!i&&t.cacheControl&&0<(n=(n=(n=t.cacheControl)&&n.match(/max-age=([0-9]+)/))?parseInt(n[1],10):0)&&(h[o]={data:t,expires:Date.now()+1e3*n}),s(e,t)},!i&&e.noCache&&"standard"===e.cdnType&&(o+=(0<=o.indexOf("?")?"&":"?")+"cache=no"),p)return g(e,o,i,t);if("function"==typeof XMLHttpRequest||"object"===("undefined"==typeof XMLHttpRequest?"undefined":l(XMLHttpRequest))||"function"==typeof ActiveXObject){var n=e,e=o,a=i,c=t;try{var u=d?new d:new f("MSXML2.XMLHTTP.3.0");u.open(a?"POST":"GET",e,1),n.crossDomain||u.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.authorize&&n.apiKey&&u.setRequestHeader("Authorization",n.apiKey),(a||n.setContentTypeJSON)&&u.setRequestHeader("Content-Type","application/json"),u.onreadystatechange=function(){var e="Error from cloudfront"===u.getResponseHeader("x-cache");if("standard"===n.cdnType&&404===u.status&&!u.getResponseHeader("x-cache"))return e=!0,3<u.readyState&&c(null,{status:200,data:"{}",resourceNotExisting:e});var t=u.getResponseHeader("Cache-Control");3<u.readyState&&c(400<=u.status?u.statusText:null,{status:u.status,data:u.responseText,resourceNotExisting:e,cacheControl:t})},u.send(JSON.stringify(a))}catch(e){console&&console.log(e)}}else t(new Error("No fetch and no xhr implementation found!"))};t.exports=n.default}.call(this)}.call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"cross-fetch":4}],3:[function(e,t,n){function c(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=Array(t);n<t;n++)o[n]=e[n];return o}Object.defineProperty(n,"__esModule",{value:!0}),n.UNSAFE_KEYS=void 0,n.debounce=function(o,i,r){var s;return function(){var e=this,t=arguments,n=r&&!s;clearTimeout(s),s=setTimeout(function(){s=null,r||o.apply(e,t)},i),n&&o.apply(e,t)}},n.defaults=function(i){return r.call(s.call(arguments,1),function(e){if(e)for(var t=0,n=Object.keys(e);t<n.length;t++){var o=n[t];-1<u.indexOf(o)||void 0===i[o]&&(i[o]=e[o])}}),i},n.defer=function(){var n,o,e=new Promise(function(e,t){n=e,o=t});return e.resolve=n,e.reject=o,e},n.getPath=function(e,t){e=i(e,t),t=e.obj,e=e.k;if(t)return t[e]},n.interpolate=function(e,t,n){var o;for(;o=d.exec(e);){i=(i=(i="string"!=typeof(i=o[1].trim())?f(i):i)||"").replace(/\$/g,"$$$$");var i=!(-1<u.indexOf(i))&&t[i]||i;e=e.replace(o[0],i),d.lastIndex=0}return e},n.interpolateUrl=function(e,t){var n,o=!1;for(;n=d.exec(e);){var i=n[1].trim();if(-1<u.indexOf(i));else{i=t[i];if(null!=i){var r,i=f(i).split("+"),s=!0,a=((e,t)=>{var n,o,i,r,s="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(s)return i=!(o=!0),{s:function(){s=s.call(e)},n:function(){var e=s.next();return o=e.done,e},e:function(e){i=!0,n=e},f:function(){try{o||null==s.return||s.return()}finally{if(i)throw n}}};if(Array.isArray(e)||(s=((e,t)=>{var n;if(e)return"string"==typeof e?c(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?c(e,t):void 0})(e))||t&&e&&"number"==typeof e.length)return s&&(e=s),r=0,{s:t=function(){},n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})(i);try{for(a.s();!(r=a.n()).done;)if(!l(r.value)){s=!1;break}}catch(e){a.e(e)}finally{a.f()}if(!s){o=!0;break}e=e.replace(n[0],i.join("+"))}}d.lastIndex=0}return d.lastIndex=0,o?null:e},n.isMissingOption=a,n.isSafeUrlSegment=l,n.optionExist=function(e,t){return!a(e,t)},n.pushPath=function(e,t,n,o){e=i(e,t,Object),t=e.obj,e=e.k;t[e]=t[e]||[],o&&(t[e]=t[e].concat(n));o||t[e].push(n)},n.redactUrlCredentials=function(t){if("string"!=typeof t||0===t.length)return t;try{var e=new URL(t);return e.username||e.password?(e.username="",e.password="",e.toString()):t}catch(e){return t.replace(/(\/\/)[^/@\s]+@/g,"$1")}},n.sanitizeLogValue=function(e){return"string"!=typeof e?e:e.replace(/[\r\n\x00-\x1F\x7F]/g," ")},n.setPath=function(e,t,n){e=i(e,t,Object);e.obj[e.k]=n};var o=[],r=o.forEach,s=o.slice,u=n.UNSAFE_KEYS=["__proto__","constructor","prototype"];function l(e){return"string"==typeof e&&!(0===e.length||128<e.length||-1<u.indexOf(e)||-1<e.indexOf("..")||-1<e.indexOf("/")||-1<e.indexOf("\\")||/[?#%\s@]/.test(e)||/[\x00-\x1F\x7F]/.test(e))}function i(e,t,n){function o(e){return e&&-1<e.indexOf("###")?e.replace(/###/g,"."):e}for(var i="string"!=typeof t?[].concat(t):t.split(".");1<i.length;){if(!e)return{};var r=o(i.shift());!e[r]&&n&&(e[r]=new n),e=e[r]}return e?{obj:e,k:o(i.shift())}:{}}var d=new RegExp("{{(.+?)}}","g");function f(e){return null==e?"":""+e}function a(n,e){return e.reduce(function(e,t){return e||(!n||!n[t]||"string"!=typeof n[t]||!n[t].toLowerCase()===t.toLowerCase())&&(e='i18next-locize-backend :: got "'.concat(n[t],'" in options for ').concat(t," which is invalid."),console.warn(e),e)},!1)}},{}],4:[function(e,t,n){},{}]},{},[1])(1)});
    \ No newline at end of file
    
  • lib/index.js+24 7 modified
    @@ -1,4 +1,4 @@
    -import { defaults, debounce, isMissingOption, interpolate, getPath, setPath, pushPath, defer } from './utils.js'
    +import { defaults, debounce, isMissingOption, interpolateUrl, getPath, setPath, pushPath, defer, sanitizeLogValue } from './utils.js'
     import request from './request.js'
     
     const getApiPaths = (cdnType) => {
    @@ -268,9 +268,13 @@ class I18NextLocizeBackend {
           return deferred
         }
     
    -    const url = interpolate(this.options.getLanguagesPath, {
    +    const url = interpolateUrl(this.options.getLanguagesPath, {
           projectId: this.options.projectId
         })
    +    if (url == null) {
    +      callback(new Error('i18next-locize-backend: unsafe projectId — refusing to build request URL for projectId=' + sanitizeLogValue(String(this.options.projectId))))
    +      return deferred
    +    }
     
         if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
           this.isProjectNotExisting = true
    @@ -293,9 +297,10 @@ class I18NextLocizeBackend {
             this.isProjectNotExistingErrorMessage = errMsg
             const cdnTypeAlt = this.options.cdnType === 'standard' ? 'pro' : 'standard'
             const otherEndpointApiPaths = getApiPaths(cdnTypeAlt)
    -        const urlAlt = interpolate(otherEndpointApiPaths.getLanguagesPath, {
    +        const urlAlt = interpolateUrl(otherEndpointApiPaths.getLanguagesPath, {
               projectId: this.options.projectId
             })
    +        if (urlAlt == null) return // already rejected above; defensive no-op
             this.loadUrl({}, urlAlt, (errAlt, retAlt, infoAlt) => {
               if (!errAlt && retAlt && (!infoAlt || !infoAlt.resourceNotExisting)) {
                 errMsg += ` It seems you're using the wrong cdnType. Your Locize project is configured to use "${cdnTypeAlt}" but here you've configured "${this.options.cdnType}".`
    @@ -412,7 +417,7 @@ class I18NextLocizeBackend {
           const isMissing = isMissingOption(this.options, ['projectId', 'version', 'apiKey'])
           if (isMissing) return callback(new Error(isMissing), false)
     
    -      url = interpolate(this.options.privatePath, {
    +      url = interpolateUrl(this.options.privatePath, {
             lng: language,
             ns: namespace,
             projectId: this.options.projectId,
    @@ -423,14 +428,18 @@ class I18NextLocizeBackend {
           const isMissing = isMissingOption(this.options, ['projectId', 'version'])
           if (isMissing) return callback(new Error(isMissing), false)
     
    -      url = interpolate(this.options.loadPath, {
    +      url = interpolateUrl(this.options.loadPath, {
             lng: language,
             ns: namespace,
             projectId: this.options.projectId,
             version: this.options.version
           })
         }
     
    +    if (url == null) {
    +      return callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to build request URL for lng=' + sanitizeLogValue(String(language)) + ' ns=' + sanitizeLogValue(String(namespace))), false)
    +    }
    +
         if (!this.isProjectNotExisting && this.storage.isProjectNotExisting(this.options.projectId)) {
           this.isProjectNotExisting = true
         }
    @@ -613,18 +622,26 @@ class I18NextLocizeBackend {
       }
     
       writePage (lng, namespace, missings, callback) {
    -    const missingUrl = interpolate(this.options.addPath, {
    +    const missingUrl = interpolateUrl(this.options.addPath, {
           lng,
           ns: namespace,
           projectId: this.options.projectId,
           version: this.options.version
         })
    -    const updatesUrl = interpolate(this.options.updatePath, {
    +    const updatesUrl = interpolateUrl(this.options.updatePath, {
           lng,
           ns: namespace,
           projectId: this.options.projectId,
           version: this.options.version
         })
    +    if (missingUrl == null || updatesUrl == null) {
    +      // Drop the queued writes silently rather than POST to an attacker-crafted
    +      // URL. Log-safe values only.
    +      if (typeof callback === 'function') {
    +        callback(new Error('i18next-locize-backend: unsafe lng/ns/projectId/version — refusing to persist missing keys for lng=' + sanitizeLogValue(String(lng)) + ' ns=' + sanitizeLogValue(String(namespace))))
    +      }
    +      return
    +    }
     
         let hasMissing = false
         let hasUpdates = false
    
  • lib/utils.js+84 2 modified
    @@ -2,17 +2,60 @@ const arr = []
     const each = arr.forEach
     const slice = arr.slice
     
    +export const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype']
    +
     export function defaults (obj) {
       each.call(slice.call(arguments, 1), (source) => {
         if (source) {
    -      for (const prop in source) {
    +      for (const prop of Object.keys(source)) {
    +        if (UNSAFE_KEYS.indexOf(prop) > -1) continue
             if (obj[prop] === undefined) obj[prop] = source[prop]
           }
         }
       })
       return obj
     }
     
    +// Returns true if `v` can be safely interpolated into a URL path segment.
    +// Denylist approach — blocks path traversal, path separators, URL-structure
    +// characters, control characters, prototype keys, and oversized inputs.
    +// `+` is allowed (used by callers to join multiple languages/namespaces).
    +export function isSafeUrlSegment (v) {
    +  if (typeof v !== 'string') return false
    +  if (v.length === 0 || v.length > 128) return false
    +  if (UNSAFE_KEYS.indexOf(v) > -1) return false
    +  if (v.indexOf('..') > -1) return false
    +  if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false
    +  if (/[?#%\s@]/.test(v)) return false
    +  // eslint-disable-next-line no-control-regex
    +  if (/[\x00-\x1F\x7F]/.test(v)) return false
    +  return true
    +}
    +
    +// Strip control characters from a string before it goes into an error
    +// message / log line (CWE-117).
    +export function sanitizeLogValue (v) {
    +  if (typeof v !== 'string') return v
    +  // eslint-disable-next-line no-control-regex
    +  return v.replace(/[\r\n\x00-\x1F\x7F]/g, ' ')
    +}
    +
    +// Redact user:password from a URL-like string before logging it.
    +export function redactUrlCredentials (u) {
    +  if (typeof u !== 'string' || u.length === 0) return u
    +  try {
    +    const parsed = new URL(u)
    +    if (parsed.username || parsed.password) {
    +      parsed.username = ''
    +      parsed.password = ''
    +      return parsed.toString()
    +    }
    +    return u
    +  } catch (e) {
    +    return u.replace(/(\/\/)[^/@\s]+@/g, '$1')
    +  }
    +}
    +
     export function debounce (func, wait, immediate) {
       let timeout
       return function () {
    @@ -92,12 +135,51 @@ export function interpolate (str, data, lng) {
         if (typeof value !== 'string') value = makeString(value)
         if (!value) value = ''
         value = regexSafe(value)
    -    str = str.replace(match[0], data[value] || value)
    +    // Skip prototype-chain key lookups on `data` — a polluted
    +    // Object.prototype.__proto__ would otherwise leak into the substitution.
    +    const subst = UNSAFE_KEYS.indexOf(value) > -1 ? value : (data[value] || value)
    +    str = str.replace(match[0], subst)
         regexp.lastIndex = 0
       }
       return str
     }
     
    +// URL-specific variant: reject values that fail isSafeUrlSegment. Returns
    +// `null` if any substitution is unsafe — callers bail out rather than issue
    +// the HTTP request. Multi-value `+` joins are validated per segment.
    +export function interpolateUrl (str, data) {
    +  let match
    +  let unsafe = false
    +  // eslint-disable-next-line no-cond-assign
    +  while (match = regexp.exec(str)) {
    +    const key = match[1].trim()
    +    if (UNSAFE_KEYS.indexOf(key) > -1) {
    +      regexp.lastIndex = 0
    +      continue
    +    }
    +    const raw = data[key]
    +    if (raw == null) {
    +      regexp.lastIndex = 0
    +      continue
    +    }
    +    const value = makeString(raw)
    +    // validate each + -separated segment independently
    +    const segments = value.split('+')
    +    let segmentsOk = true
    +    for (const seg of segments) {
    +      if (!isSafeUrlSegment(seg)) { segmentsOk = false; break }
    +    }
    +    if (!segmentsOk) {
    +      unsafe = true
    +      break
    +    }
    +    str = str.replace(match[0], segments.join('+'))
    +    regexp.lastIndex = 0
    +  }
    +  regexp.lastIndex = 0
    +  return unsafe ? null : str
    +}
    +
     export function isMissingOption (obj, props) {
       return props.reduce((mem, p) => {
         if (mem) return mem
    
  • package.json+6 6 modified
    @@ -42,22 +42,22 @@
       },
       "types": "./index.d.mts",
       "devDependencies": {
    -    "@babel/cli": "7.28.3",
    -    "@babel/core": "7.28.5",
    -    "@babel/preset-env": "7.28.5",
    +    "@babel/cli": "7.28.6",
    +    "@babel/core": "7.29.0",
    +    "@babel/preset-env": "7.29.2",
         "babel-plugin-add-module-exports": "1.0.4",
         "browserify": "17.0.1",
         "dtslint": "4.2.1",
         "eslint": "9.39.1",
         "eslint-plugin-import": "2.32.0",
         "expect.js": "0.3.1",
    -    "i18next": "25.7.2",
    +    "i18next": "26.0.6",
         "json-server": "0.17.4",
         "mocha": "11.7.5",
    -    "neostandard": "0.12.2",
    +    "neostandard": "0.13.0",
         "tslint": "5.20.1",
         "tsd": "0.33.0",
    -    "typescript": "5.9.3",
    +    "typescript": "6.0.3",
         "uglify-js": "3.19.3",
         "xmlhttprequest": "1.8.0"
       },
    
  • test/security.spec.js+122 0 added
    @@ -0,0 +1,122 @@
    +import expect from 'expect.js'
    +import {
    +  interpolate,
    +  interpolateUrl,
    +  isSafeUrlSegment,
    +  sanitizeLogValue,
    +  redactUrlCredentials
    +} from '../lib/utils.js'
    +
    +// Security tests for the 9.0.2 hardening.
    +
    +describe('security', () => {
    +  describe('isSafeUrlSegment', () => {
    +    it('accepts arbitrary language / project / version codes', () => {
    +      expect(isSafeUrlSegment('en')).to.be(true)
    +      expect(isSafeUrlSegment('de-DE')).to.be(true)
    +      expect(isSafeUrlSegment('en_US')).to.be(true)
    +      expect(isSafeUrlSegment('zh-Hant-HK')).to.be(true)
    +      expect(isSafeUrlSegment('pirate-speak')).to.be(true)
    +      expect(isSafeUrlSegment('my-custom.ns')).to.be(true)
    +      // projectId UUID shape
    +      expect(isSafeUrlSegment('abc1d4f3-d0a7-4794-b74f-b72134d82d35')).to.be(true)
    +      // version strings
    +      expect(isSafeUrlSegment('latest')).to.be(true)
    +      expect(isSafeUrlSegment('production')).to.be(true)
    +    })
    +    it('rejects traversal / separator / URL-structure / control chars / prototype keys', () => {
    +      expect(isSafeUrlSegment('../etc/passwd')).to.be(false)
    +      expect(isSafeUrlSegment('..')).to.be(false)
    +      expect(isSafeUrlSegment('foo/bar')).to.be(false)
    +      expect(isSafeUrlSegment('foo\\bar')).to.be(false)
    +      expect(isSafeUrlSegment('en?admin=true')).to.be(false)
    +      expect(isSafeUrlSegment('en#frag')).to.be(false)
    +      expect(isSafeUrlSegment('en%2F..')).to.be(false)
    +      expect(isSafeUrlSegment('en evil')).to.be(false)
    +      expect(isSafeUrlSegment('en@host')).to.be(false)
    +      expect(isSafeUrlSegment('__proto__')).to.be(false)
    +      expect(isSafeUrlSegment('en\r\nX-Injected: bad')).to.be(false)
    +      expect(isSafeUrlSegment('')).to.be(false)
    +      expect(isSafeUrlSegment('a'.repeat(200))).to.be(false)
    +    })
    +  })
    +
    +  describe('interpolate (guarded)', () => {
    +    it('does not leak prototype-chain properties on __proto__ lookups', () => {
    +      const out = interpolate('x/{{__proto__}}/y', { __proto__: { polluted: true } })
    +      // Should return the key itself, not dereference __proto__
    +      expect(out).to.equal('x/__proto__/y')
    +    })
    +    it('substitutes normal keys', () => {
    +      expect(interpolate('/{{projectId}}/{{lng}}', { projectId: 'abc', lng: 'en' }))
    +        .to.equal('/abc/en')
    +    })
    +  })
    +
    +  describe('interpolateUrl', () => {
    +    const template = 'https://api.locize.app/{{projectId}}/{{version}}/{{lng}}/{{ns}}'
    +
    +    it('accepts a plain URL', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: 'abc1d4f3-d0a7-4794-b74f-b72134d82d35',
    +        version: 'latest',
    +        lng: 'en',
    +        ns: 'common'
    +      })).to.equal('https://api.locize.app/abc1d4f3-d0a7-4794-b74f-b72134d82d35/latest/en/common')
    +    })
    +
    +    it('accepts + joins', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: 'p', version: 'v', lng: 'en+de', ns: 'a+b'
    +      })).to.equal('https://api.locize.app/p/v/en+de/a+b')
    +    })
    +
    +    it('returns null for path traversal in lng', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: 'p', version: 'v', lng: '../../etc', ns: 'x'
    +      })).to.equal(null)
    +    })
    +
    +    it('returns null for path traversal in projectId', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: '../admin', version: 'v', lng: 'en', ns: 'x'
    +      })).to.equal(null)
    +    })
    +
    +    it('returns null for query-string / fragment injection', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: 'p', version: 'v', lng: 'en?admin=true', ns: 'x'
    +      })).to.equal(null)
    +      expect(interpolateUrl(template, {
    +        projectId: 'p', version: 'v', lng: 'en#frag', ns: 'x'
    +      })).to.equal(null)
    +    })
    +
    +    it('returns null when any + segment is unsafe', () => {
    +      expect(interpolateUrl(template, {
    +        projectId: 'p', version: 'v', lng: 'en+../etc/passwd', ns: 'x'
    +      })).to.equal(null)
    +    })
    +  })
    +
    +  describe('sanitizeLogValue', () => {
    +    it('strips CR, LF, NUL and other control chars', () => {
    +      expect(sanitizeLogValue('en\r\n2026-04-18 admin login'))
    +        .to.equal('en  2026-04-18 admin login')
    +      expect(sanitizeLogValue('en\u0000')).to.equal('en ')
    +    })
    +    it('passes non-strings through unchanged', () => {
    +      expect(sanitizeLogValue(undefined)).to.equal(undefined)
    +      expect(sanitizeLogValue(42)).to.equal(42)
    +    })
    +  })
    +
    +  describe('redactUrlCredentials', () => {
    +    it('strips user:password from URLs', () => {
    +      expect(redactUrlCredentials('https://user:pass@api.locize.app/p/v/en/x'))
    +        .to.equal('https://api.locize.app/p/v/en/x')
    +      expect(redactUrlCredentials('https://api.locize.app/p/v/en/x'))
    +        .to.equal('https://api.locize.app/p/v/en/x')
    +    })
    +  })
    +})
    
  • tsconfig.json+3 2 modified
    @@ -1,13 +1,14 @@
     {
       "compilerOptions": {
         "module": "commonjs",
    -    "target": "es5",
    -    "lib": ["es6", "dom"],
    +    "target": "es2017",
    +    "lib": ["es2017", "dom"],
         "jsx": "react",
         "moduleResolution": "node",
         "forceConsistentCasingInFileNames": true,
         "strict": true,
         "noEmit": true,
    +    "ignoreDeprecations": "6.0",
         "baseUrl": ".",
         "paths": { "i18next-locize-backend": ["./index.d.mts"] },
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.