function recursiveReplaceLocalizableStrings(obj, cb, seen) {
      if (seen.has(seen)) {
        return;
      }

      seen.add(obj);

      if (obj instanceof Map) {
        for (const [key, value] of obj) {
          if (value && typeof value === 'object' && 'i18nId' in value) {
            obj.set(key, cb(value));
          } else {
            recursiveReplaceLocalizableStrings(value, cb, seen);
          }
        }
      } else if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
        Object.keys(obj).forEach(key => {
          const value = obj[key];
          if (value && typeof value === 'object' && 'i18nId' in value) {
            obj[key] = cb(value);
          } else {
            recursiveReplaceLocalizableStrings(value, cb, seen);
          }
        });
      } else if (Array.isArray(obj)) {
        for (let i = 0; i < obj.length; i++) {
          const value = obj[i];
          if (value && typeof value === 'object' && 'i18nId' in value) {
            obj[i] = cb(value);
          } else {
            recursiveReplaceLocalizableStrings(value, cb, seen);
          }
        }
      }
    }
Execute `cb(traceEngineI18nObject)` on every i18n object, recursively. The cb return
value replaces traceEngineI18nObject.
@param {any} obj
@param {(traceEngineI18nObject: {i18nId: string, values?: {}}) => LH.IcuMessage} cb
@param {Set<object>} seen