[
  {
    "path": ".babelrc",
    "content": "{\n  \"comments\": false,\n  \"env\": {\n    \"main\": {\n      \"presets\": [\"@babel/preset-env\"]\n    },\n    \"renderer\": {\n      \"presets\": [\n        \"@babel/preset-env\"\n      ],\n      \"plugins\": [\n        [\n          \"component\",\n          {\n            \"libraryName\": \"element-ui\",\n            \"styleLibraryName\": \"theme-chalk\"\n          }\n        ]\n      ]\n    },\n    \"web\": {\n      \"presets\": [\"@babel/preset-env\"],\n      \"plugins\": [\n        [\n          \"component\",\n          {\n            \"libraryName\": \"element-ui\",\n            \"styleLibraryName\": \"theme-chalk\"\n          }\n        ]\n      ]\n    }\n  },\n  \"plugins\": [\n    \"@babel/plugin-proposal-class-properties\",\n    \"@babel/plugin-transform-runtime\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".electron-vue/build.js",
    "content": "'use strict'\n\nprocess.env.NODE_ENV = 'production'\n\nconst { say } = require('cfonts')\nconst chalk = require('chalk')\nconst del = require('del')\nconst Webpack = require('webpack')\nconst Multispinner = require('@motrix/multispinner')\n\nconst mainConfig = require('./webpack.main.config')\nconst rendererConfig = require('./webpack.renderer.config')\nconst webConfig = require('./webpack.web.config')\n\nconst doneLog = chalk.bgGreen.white(' DONE ') + ' '\nconst errorLog = chalk.bgRed.white(' ERROR ') + ' '\nconst okayLog = chalk.bgBlue.white(' OKAY ') + ' '\nconst isCI = process.env.CI || false\n\nif (process.env.BUILD_TARGET === 'clean') {\n  clean()\n} else if (process.env.BUILD_TARGET === 'web') {\n  web()\n} else {\n  build()\n}\n\nfunction clean () {\n  del.sync(['release/*', '!.gitkeep'])\n  console.log(`\\n${doneLog}\\n`)\n  process.exit()\n}\n\nfunction build () {\n  greeting()\n\n  del.sync(['dist/electron/*', '!.gitkeep'])\n\n  const tasks = ['main', 'renderer']\n  const m = new Multispinner(tasks, {\n    preText: 'building',\n    postText: 'process'\n  })\n\n  let results = ''\n\n  m.on('success', () => {\n    process.stdout.write('\\x1B[2J\\x1B[0f')\n    console.log(`\\n\\n${results}`)\n    console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\\n`)\n    process.exit()\n  })\n\n  pack(mainConfig).then(result => {\n    results += result + '\\n\\n'\n    m.success('main')\n  }).catch(err => {\n    m.error('main')\n    console.log(`\\n  ${errorLog}failed to build main process`)\n    console.error(`\\n${err}\\n`)\n    process.exit(1)\n  })\n\n  pack(rendererConfig).then(result => {\n    results += result + '\\n\\n'\n    m.success('renderer')\n  }).catch(err => {\n    m.error('renderer')\n    console.log(`\\n  ${errorLog}failed to build renderer process`)\n    console.error(`\\n${err}\\n`)\n    process.exit(1)\n  })\n}\n\nfunction pack (config) {\n  return new Promise((resolve, reject) => {\n    config.mode = 'production'\n    Webpack(config, (err, stats) => {\n      if (err) {\n        reject(err.stack || err)\n      } else if (stats.hasErrors()) {\n        let err = ''\n\n        stats.toString({\n          chunks: false,\n          colors: true\n        })\n        .split(/\\r?\\n/)\n        .forEach(line => {\n          err += `    ${line}\\n`\n        })\n\n        reject(err)\n      } else {\n        resolve(stats.toString({\n          chunks: false,\n          colors: true\n        }))\n      }\n    })\n  })\n}\n\nfunction web () {\n  deleteSync(['dist/web/*', '!.gitkeep'])\n  webConfig.mode = 'production'\n  Webpack(webConfig, (err, stats) => {\n    if (err || stats.hasErrors()) console.log(err)\n\n    console.log(stats.toString({\n      chunks: false,\n      colors: true\n    }))\n\n    process.exit()\n  })\n}\n\nfunction greeting () {\n  const cols = process.stdout.columns\n  let text = ''\n\n  if (cols > 85) {\n    text = 'lets-build'\n  } else if (cols > 60) {\n    text = 'lets-|build'\n  } else {\n    text = false\n  }\n\n  if (text && !isCI) {\n    say(text, {\n      colors: ['magentaBright'],\n      font: 'simple3d',\n      space: false\n    })\n  } else console.log(chalk.magentaBright.bold('\\n  lets-build'))\n  console.log()\n}\n"
  },
  {
    "path": ".electron-vue/dev-client.js",
    "content": "const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')\n\nhotClient.subscribe(event => {\n  /**\n   * Reload browser when HTMLWebpackPlugin emits a new index.html\n   *\n   * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.\n   * https://github.com/SimulatedGREG/electron-vue/issues/437\n   * https://github.com/jantimon/html-webpack-plugin/issues/680\n   */\n  // if (event.action === 'reload') {\n  //   window.location.reload()\n  // }\n\n  /**\n   * Notify `mainWindow` when `main` process is compiling,\n   * giving notice for an expected reload of the `electron` process\n   */\n  if (event.action === 'compiling') {\n    document.body.innerHTML += `\n      <style>\n        #dev-client {\n          background: #4fc08d;\n          border-radius: 4px;\n          bottom: 20px;\n          box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);\n          color: #fff;\n          font-family: 'Source Sans Pro', sans-serif;\n          left: 20px;\n          padding: 8px 12px;\n          position: absolute;\n        }\n      </style>\n\n      <div id=\"dev-client\">\n        Compiling Main Process...\n      </div>\n    `\n  }\n})\n"
  },
  {
    "path": ".electron-vue/dev-runner.js",
    "content": "'use strict'\n\nconst path = require('node:path')\nconst { spawn } = require('node:child_process')\nconst { say } = require('cfonts')\nconst electron = require('electron')\nconst chalk = require('chalk')\nconst Webpack = require('webpack')\nconst WebpackDevServer = require('webpack-dev-server')\n\nconst mainConfig = require('./webpack.main.config')\nconst rendererConfig = require('./webpack.renderer.config')\n\nlet electronProcess = null\nlet manualRestart = false\n\nfunction logStats (proc, data) {\n  let log = ''\n\n  log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)\n  log += '\\n\\n'\n\n  if (typeof data === 'object') {\n    data.toString({\n      colors: true,\n      chunks: false\n    }).split(/\\r?\\n/).forEach(line => {\n      log += '  ' + line + '\\n'\n    })\n  } else {\n    log += `  ${data}\\n`\n  }\n\n  log += '\\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\\n'\n\n  console.log(log)\n}\n\nfunction startRenderer () {\n  return new Promise(async (resolve, reject) => {\n    rendererConfig.entry.index = rendererConfig.entry.index\n    rendererConfig.mode = 'development'\n\n    const compiler = Webpack(rendererConfig)\n    const devServerOptions = {\n      ...rendererConfig.devServer,\n      port: 9080,\n      static: {\n        directory: path.resolve(__dirname, \"../\"),\n      },\n    };\n\n    const server = new WebpackDevServer(devServerOptions, compiler)\n    await server.start()\n    resolve()\n  })\n}\n\nfunction startMain () {\n  return new Promise((resolve, reject) => {\n    mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)\n    mainConfig.mode = 'development'\n    const compiler = Webpack(mainConfig)\n\n    compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {\n      logStats('Main', chalk.white.bold('compiling...'))\n      // hotMiddleware.publish({ action: 'compiling' })\n      done()\n    })\n\n    compiler.watch({}, (err, stats) => {\n      if (err) {\n        console.log(err)\n        return\n      }\n\n      logStats('Main', stats)\n\n      if (electronProcess && electronProcess.kill) {\n        manualRestart = true\n        process.kill(electronProcess.pid)\n        electronProcess = null\n        startElectron()\n\n        setTimeout(() => {\n          manualRestart = false\n        }, 5000)\n      }\n\n      resolve()\n    })\n  })\n}\n\nfunction startElectron () {\n  electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')])\n\n  electronProcess.stdout.on('data', data => {\n    electronLog(data, 'blue')\n  })\n  electronProcess.stderr.on('data', data => {\n    electronLog(data, 'red')\n  })\n\n  electronProcess.on('close', () => {\n    if (!manualRestart) process.exit()\n  })\n}\n\nfunction electronLog (data, color) {\n  let log = ''\n  data = data.toString().split(/\\r?\\n/)\n  data.forEach(line => {\n    log += `  ${line}\\n`\n  })\n  if (/[0-9A-z]+/.test(log)) {\n    console.log(\n      chalk[color].bold('┏ Electron -------------------') +\n      '\\n\\n' +\n      log +\n      chalk[color].bold('┗ ----------------------------') +\n      '\\n'\n    )\n  }\n}\n\nfunction greeting () {\n  const cols = process.stdout.columns\n  let text = ''\n\n  if (cols > 104) {\n    text = 'motrix-dev'\n  } else if (cols > 76) {\n    text = 'motrix-|dev'\n  } else {\n    text = false\n  }\n\n  if (text) {\n    say(text, {\n      colors: ['magentaBright'],\n      font: 'simple3d',\n      space: false\n    })\n  } else console.log(chalk.magentaBright.bold('\\n  motrix-dev'))\n  console.log(chalk.blue('  getting ready...') + '\\n')\n}\n\nfunction init () {\n  greeting()\n\n  Promise.all([startRenderer(), startMain()])\n    .then(() => {\n      startElectron()\n    })\n    .catch(err => {\n      console.error(err)\n    })\n}\n\ninit()\n"
  },
  {
    "path": ".electron-vue/webpack.main.config.js",
    "content": "'use strict'\n\nprocess.env.BABEL_ENV = 'main'\n\nconst path = require('node:path')\nconst Webpack = require('webpack')\nconst ESLintPlugin = require('eslint-webpack-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\nconst { dependencies } = require('../package.json')\nconst { appId } = require('../electron-builder.json')\nconst devMode = process.env.NODE_ENV !== 'production'\n\nlet mainConfig = {\n  entry: {\n    main: path.join(__dirname, '../src/main/index.js')\n  },\n  externals: [\n    ...Object.keys(dependencies || {})\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        use: 'babel-loader',\n        exclude: /node_modules/\n      },\n      {\n        test: /\\.node$/,\n        use: 'node-loader'\n      }\n    ]\n  },\n  node: {\n    __dirname: devMode,\n    __filename: devMode\n  },\n  output: {\n    filename: '[name].js',\n    libraryTarget: 'commonjs2',\n    path: path.join(__dirname, '../dist/electron')\n  },\n  plugins: [\n    new Webpack.NoEmitOnErrorsPlugin(),\n    new ESLintPlugin({\n      formatter: require('eslint-friendly-formatter')\n    })\n  ],\n  resolve: {\n    alias: {\n      '@': path.join(__dirname, '../src/main'),\n      '@shared': path.join(__dirname, '../src/shared')\n    },\n    extensions: ['.js', '.json', '.node']\n  },\n  target: 'electron-main',\n  optimization: {\n    minimize: !devMode,\n    minimizer: [\n      new TerserPlugin({\n        extractComments: false,\n      })\n    ],\n  },\n}\n\n/**\n * Adjust mainConfig for development settings\n */\nif (devMode) {\n  mainConfig.plugins.push(\n    new Webpack.DefinePlugin({\n      '__static': `\"${path.join(__dirname, '../static').replace(/\\\\/g, '\\\\\\\\')}\"`,\n      'appId': `\"${appId}\"`\n    })\n  )\n}\n\n/**\n * Adjust mainConfig for production settings\n */\nif (!devMode) {\n  mainConfig.plugins.push(\n    new Webpack.DefinePlugin({\n      'process.env.NODE_ENV': '\"production\"',\n      'appId': `\"${appId}\"`\n    })\n  )\n}\n\nmodule.exports = mainConfig\n"
  },
  {
    "path": ".electron-vue/webpack.renderer.config.js",
    "content": "'use strict'\n\nprocess.env.BABEL_ENV = 'renderer'\n\nconst path = require('node:path')\nconst Webpack = require('webpack')\nconst { VueLoaderPlugin } = require('vue-loader')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin')\nconst ESLintPlugin = require('eslint-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\nconst { dependencies } = require('../package.json')\nconst devMode = process.env.NODE_ENV !== 'production'\n\n/**\n * List of node_modules to include in webpack bundle\n *\n * Required for specific packages like Vue UI libraries\n * that provide pure *.vue files that need compiling\n * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals\n */\nlet whiteListedModules = ['vue']\n\nlet rendererConfig = {\n  entry: {\n    index: path.join(__dirname, '../src/renderer/pages/index/main.js')\n  },\n  externals: [\n    ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.worker\\.js$/,\n        use: {\n          loader: 'worker-loader',\n          options: { filename: '[name].js' }\n        }\n      },\n      {\n        test: /\\.scss$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              implementation: require('sass'),\n              additionalData: '@import \"@/components/Theme/Variables.scss\";',\n              sassOptions: {\n                includePaths:[__dirname, 'src']\n              }\n            },\n          }\n        ]\n      },\n      {\n        test: /\\.sass$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              implementation: require('sass'),\n              indentedSyntax: true,\n              additionalData: '@import \"@/components/Theme/Variables.scss\";',\n              sassOptions: {\n                includePaths:[__dirname, 'src']\n              }\n            },\n          }\n        ]\n      },\n      {\n        test: /\\.less$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          'less-loader'\n        ]\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader'\n        ]\n      },\n      {\n        test: /\\.js$/,\n        use: 'babel-loader',\n        exclude: /node_modules/\n      },\n      {\n        test: /\\.node$/,\n        use: 'node-loader'\n      },\n      {\n        test: /\\.vue$/,\n        use: {\n          loader: 'vue-loader',\n          options: {\n            extractCSS: process.env.NODE_ENV === 'production',\n            loaders: {\n              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',\n              scss: 'vue-style-loader!css-loader!sass-loader',\n              less: 'vue-style-loader!css-loader!less-loader'\n            }\n          }\n        }\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        type: 'asset/inline'\n      },\n      {\n        test: /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/,\n        type: 'asset/resource'\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        type: 'asset/inline'\n      }\n    ]\n  },\n  node: {\n    __dirname: devMode,\n    __filename: devMode\n  },\n  plugins: [\n    new VueLoaderPlugin(),\n    new MiniCssExtractPlugin({\n      filename: '[name].css',\n      chunkFilename: '[id].css'\n    }),\n    new HtmlWebpackPlugin({\n      title: 'Motrix',\n      filename: 'index.html',\n      chunks: ['index'],\n      template: path.resolve(__dirname, '../src/index.ejs'),\n      // minify: {\n      //   collapseWhitespace: true,\n      //   removeAttributeQuotes: true,\n      //   removeComments: true\n      // },\n      isBrowser: false,\n      isDev: process.env.NODE_ENV !== 'production',\n      nodeModules: devMode\n        ? path.resolve(__dirname, '../node_modules')\n        : false\n    }),\n    new Webpack.HotModuleReplacementPlugin(),\n    new Webpack.NoEmitOnErrorsPlugin(),\n    new ESLintPlugin({\n      extensions: ['js', 'vue'],\n      formatter: require('eslint-friendly-formatter')\n    })\n  ],\n  output: {\n    filename: '[name].js',\n    libraryTarget: 'commonjs2',\n    path: path.join(__dirname, '../dist/electron'),\n    globalObject: 'this',\n    publicPath: ''\n  },\n  resolve: {\n    alias: {\n      '@': path.join(__dirname, '../src/renderer'),\n      '@shared': path.join(__dirname, '../src/shared'),\n      'vue$': 'vue/dist/vue.esm.js'\n    },\n    extensions: ['.js', '.vue', '.json', '.css', '.node']\n  },\n  target: 'electron-renderer',\n  optimization: {\n    minimize: !devMode,\n    minimizer: [\n      new TerserPlugin({\n        extractComments: false,\n      }),\n      new CssMinimizerPlugin(),\n    ],\n  },\n}\n\n/**\n * Adjust rendererConfig for development settings\n */\nif (devMode) {\n  rendererConfig.devtool = 'eval-cheap-module-source-map'\n\n  rendererConfig.plugins.push(\n    new Webpack.DefinePlugin({\n      '__static': `\"${path.join(__dirname, '../static').replace(/\\\\/g, '\\\\\\\\')}\"`\n    })\n  )\n}\n\n/**\n * Adjust rendererConfig for production settings\n */\nif (!devMode) {\n  rendererConfig.plugins.push(\n    new CopyWebpackPlugin({\n      patterns: [{\n        from: path.join(__dirname, '../static'),\n        to: path.join(__dirname, '../dist/electron/static'),\n        globOptions: { ignore: [ '.*' ] }\n      }]\n    }),\n    new Webpack.DefinePlugin({\n      'process.env.NODE_ENV': '\"production\"'\n    }),\n    new Webpack.LoaderOptionsPlugin({\n      minimize: false\n    })\n  )\n}\n\nmodule.exports = rendererConfig\n"
  },
  {
    "path": ".electron-vue/webpack.web.config.js",
    "content": "'use strict'\n\nprocess.env.BABEL_ENV = 'web'\n\nconst path = require('node:path')\nconst { dependencies } = require('../package.json')\nconst Webpack = require('webpack')\nconst { VueLoaderPlugin } = require('vue-loader')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin')\nconst ESLintPlugin = require('eslint-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\nconst devMode = process.env.NODE_ENV !== 'production'\n\n/**\n * List of node_modules to include in webpack bundle\n *\n * Required for specific packages like Vue UI libraries\n * that provide pure *.vue files that need compiling\n * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals\n */\nlet whiteListedModules = ['vue']\n\nlet webConfig = {\n  entry: {\n    index: path.join(__dirname, '../src/renderer/pages/index/main.js')\n  },\n  externals: [\n    ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.worker\\.js$/,\n        use: {\n          loader: 'worker-loader',\n          options: { filename: '[name].js' }\n        }\n      },\n      {\n        test: /\\.scss$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              implementation: require('sass'),\n              additionalData: '@import \"@/components/Theme/Variables.scss\"',\n              sassOptions: {\n                includePaths:[__dirname, 'src']\n              }\n            },\n          }\n        ]\n      },\n      {\n        test: /\\.sass$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              implementation: require('sass'),\n              indentedSyntax: true,\n              additionalData: '@import \"@/components/Theme/Variables.scss\"',\n              sassOptions: {\n                includePaths:[__dirname, 'src']\n              }\n            },\n          }\n        ]\n      },\n      {\n        test: /\\.less$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader',\n          'less-loader'\n        ]\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,\n          'css-loader'\n        ]\n      },\n      {\n        test: /\\.js$/,\n        use: 'babel-loader',\n        include: [ path.resolve(__dirname, '../src/renderer') ],\n        exclude: /node_modules/\n      },\n      {\n        test: /\\.vue$/,\n        use: {\n          loader: 'vue-loader',\n          options: {\n            extractCSS: true,\n            loaders: {\n              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',\n              scss: 'vue-style-loader!css-loader!sass-loader',\n              less: 'vue-style-loader!css-loader!less-loader'\n            }\n          }\n        }\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        type: 'asset/inline'\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        type: 'asset/inline'\n      }\n    ]\n  },\n  plugins: [\n    new VueLoaderPlugin(),\n    new MiniCssExtractPlugin({\n      filename: '[name].css',\n      chunkFilename: '[id].css'\n    }),\n    new HtmlWebpackPlugin({\n      title: 'Motrix',\n      filename: 'index.html',\n      chunks: ['index'],\n      template: path.resolve(__dirname, '../src/index.ejs'),\n      // minify: {\n      //   collapseWhitespace: true,\n      //   removeAttributeQuotes: true,\n      //   removeComments: true\n      // },\n      isBrowser: true,\n      isDev: process.env.NODE_ENV !== 'production',\n      nodeModules: devMode\n        ? path.resolve(__dirname, '../node_modules')\n        : false\n    }),\n    new Webpack.DefinePlugin({\n      'process.env.IS_WEB': 'true'\n    }),\n    new Webpack.HotModuleReplacementPlugin(),\n    new Webpack.NoEmitOnErrorsPlugin(),\n    new ESLintPlugin({\n      extensions: ['js', 'vue'],\n      formatter: require('eslint-friendly-formatter')\n    })\n  ],\n  output: {\n    filename: '[name].js',\n    path: path.join(__dirname, '../dist/web'),\n    globalObject: 'this',\n    publicPath: ''\n  },\n  resolve: {\n    alias: {\n      '@': path.join(__dirname, '../src/renderer'),\n      '@shared': path.join(__dirname, '../src/shared'),\n      'vue$': 'vue/dist/vue.esm.js'\n    },\n    extensions: ['.js', '.vue', '.json', '.css']\n  },\n  target: 'web',\n  optimization: {\n    minimize: !devMode,\n    minimizer: [\n      new TerserPlugin({\n        extractComments: false,\n      }),\n      new CssMinimizerPlugin(),\n    ],\n  },\n}\n\n/**\n * Adjust webConfig for development settings\n */\nif (devMode) {\n  webConfig.devtool = 'eval-cheap-module-source-map'\n}\n\n/**\n * Adjust webConfig for production settings\n */\nif (!devMode) {\n  webConfig.plugins.push(\n    new CopyWebpackPlugin({\n      patterns: [{\n        from: path.join(__dirname, '../static'),\n        to: path.join(__dirname, '../dist/electron/static'),\n        globOptions: { ignore: [ '.*' ] }\n      }]\n    }),\n    new Webpack.DefinePlugin({\n      'process.env.NODE_ENV': '\"production\"'\n    }),\n    new Webpack.LoaderOptionsPlugin({\n      minimize: true\n    })\n  )\n}\n\nmodule.exports = webConfig\n"
  },
  {
    "path": ".eslintignore",
    "content": "src/renderer/components/Icons/*.js\n\nsrc/shared/locales/*\n!src/shared/locales/all.js\n!src/shared/locales/app.js\n!src/shared/locales/index.js\n!src/shared/locales/LocalManager.js\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    browser: true,\n    node: true\n  },\n  extends: [\n    'plugin:vue/essential',\n    '@vue/standard'\n  ],\n  parserOptions: {\n    parser: 'babel-eslint'\n  },\n  globals: {\n    appId: true,\n    __static: true\n  },\n  rules: {\n    'no-console': 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',\n    indent: ['error', 2],\n    'vue/script-indent': ['error', 2, {\n      baseIndent: 1\n    }]\n  },\n  overrides: [\n    {\n      files: ['*.vue'],\n      rules: {\n        indent: 'off'\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1_bug_report.yml",
    "content": "name: 🐛 [NEW] Bug Report\ndescription: File a bug report here\ntitle: \"[BUG]: \"\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report 🤗\n        Make sure there aren't any open/closed issues for this topic 😃\n\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: Description of the bug\n      description: Give us a brief description of what happened and what should have happened\n    validations:\n      required: true\n\n  - type: textarea\n    id: app-version\n    attributes:\n      label: Motrix Version\n      description: Please provide detailed version information and installation method, such as macOS Apple silicon dmg, Windows Universal installation file, etc.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Environment\n      description: |\n        Run this command in your project's root folder and paste the result:\n\n        ```sh\n        npx envinfo --system --binaries --browsers\n        ```\n        add `| pbcopy` if you're in macOS for easy copy paste.\n        Alternatively, you can manually gather the version information from your environment.\n    validations:\n      required: true\n\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: Steps To Reproduce\n      description: Steps to reproduce the behavior.\n      placeholder: |\n        1. Go to '...'\n        2. Click on '...'\n        3. Scroll down to '...'\n        4. See error\n        More info: A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is **required**, otherwise the issue might be closed without further notice. [**Why & How?**](https://antfu.me/posts/why-reproductions-are-required)\n    validations:\n      required: true\n\n  - type: textarea\n    id: additional-information\n    attributes:\n      label: Additional Information\n      description: |\n        Provide any additional information such as logs, screenshots, likes, scenarios in which the bug occurs so that it facilitates resolving the issue.\n\n  - type: checkboxes\n    id: checkboxes\n    attributes:\n      label: Validations\n      description: Before submitting the issue, please make sure you do the following\n      options:\n        - label: Follow our [Code of Conduct](https://github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)\n          required: true\n        - label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.\n          required: true\n        - label: Check that this is a concrete bug. For Q&A, please open a [GitHub Discussion](https://github.com/agalwood/Motrix/discussions) instead.\n          required: true\n        - label: The provided reproduction is a [minimal reproducible](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2_bug_report_cn.yml",
    "content": "name: 🐛 [新] Bug 报告\ndescription: 在这里提交 Bug 报告\ntitle: \"[BUG]: \"\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        感谢您抽出时间来填写这份 Bug 报告 🤗\n        请确保此问题没有已存在的开放/关闭问题 😃\n\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: Bug 描述\n      description: 给我们一个简短的描述，说明发生了什么以及应该发生什么。\n    validations:\n      required: true\n\n  - type: textarea\n    id: app-version\n    attributes:\n      label: Motrix 版本\n      description: 请提供详细的版本信息以及安装的方式，如 macOS Apple silicon dmg、Windows Universal 安装文件等\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: 环境\n      description: |\n        在项目的根目录下运行以下命令，并将结果粘贴到下方：\n\n        ```sh\n        npx envinfo --system --binaries --browsers\n        ```\n        在 macOS 中，如果您需要轻松复制粘贴，可以添加 `| pbcopy`。\n        或者，您也可以手动收集您的环境版本信息。\n    validations:\n      required: true\n\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: 复现步骤\n      description: 复现该问题的步骤。\n      placeholder: |\n        1. 前往 '...'\n        2. 点击 '...'\n        3. 滚动到 '...'\n        4. 查看错误\n        更多信息：[最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example) 是必需的，否则该问题可能会被关闭而没有进一步的通知。[**为什么 & 如何？**](https://antfu.me/posts/why-reproductions-are-required)\n    validations:\n      required: true\n\n  - type: textarea\n    id: additional-information\n    attributes:\n      label: 额外信息\n      description: |\n        提供任何额外的信息，例如日志、截图、喜欢、发生该 Bug 的场景，以便有助于解决问题。\n\n  - type: checkboxes\n    id: checkboxes\n    attributes:\n      label: 验证\n      description: 在提交问题之前，请确保您完成了以下操作\n      options:\n        - label: 遵循我们的[行为准则](https：//github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)\n          required: true\n        - label: 确认是否已经有一个报告了相同的 Bug，以避免创建重复的问题。\n          required: true\n        - label: 确认此问题是一个具体的 Bug。若要进行问答，请开启 [GitHub 讨论](https://github.com/agalwood/Motrix/discussions)。\n          required: true\n        - label: 提供的复现是该 Bug 的 [最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example)。\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement ✨\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request_cn.md",
    "content": "---\nname: 新功能请求\nabout: 你期望 Motrix 未来添加的新功能\ntitle: ''\nlabels: enhancement ✨\nassignees: ''\n\n---\n\n<!--\n反馈之前请搜索一下已有 issues 和 帮助文档，看是否已经有人提交了类似的新功能请求\nhttps://github.com/agalwood/Motrix/issues\nhttp://motrix.app/support\n\n按以下格式填写反馈信息，谢谢\n-->\n\n**请描述一下你的新功能请求是否与已知问题有关？**\n简明扼要地描述了问题所在。\n\n**描述你想要的解决方案**\n简明扼要地描述你想要的解决方案。\n\n**描述你考虑过的替代方案**\n简明扼要地描述你考虑过的任何替代解决方案或功能。\n\n**更多信息**\n补充有关该新功能的其他信息。\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- You can erase any parts of this template not applicable to your Pull Request. -->\n\n## Description\n<!-- Write a brief description of the changes introduced by this PR -->\n\n## Related Issues\n<!--\n  Link to the issue that is fixed by this PR (if there is one)\n  e.g. Fixes #1234, Addresses #1234, Related to #1234, etc.\n-->\n\n### Checklist:\n\n* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../../pulls) for the same update/change?\n* [ ] Have you linted your code locally prior to submission?\n* [ ] Have you successfully ran app with your changes locally?\n"
  },
  {
    "path": ".github/lock.yml",
    "content": "# Configuration for Lock Threads - https://github.com/dessant/lock-threads\n\n# Number of days of inactivity before a closed issue or pull request is locked\ndaysUntilLock: 60\n\n# Skip issues and pull requests created before a given timestamp. Timestamp must\n# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable\nskipCreatedBefore: false\n\n# Issues and pull requests with these labels will be ignored. Set to `[]` to disable\nexemptLabels: []\n\n# Label to add before locking, such as `outdated`. Set to `false` to disable\nlockLabel: false\n\n# Comment to post before locking. Set to `false` to disable\nlockComment: >\n  This thread has been automatically locked since there has not been\n  any recent activity after it was closed. Please open a new issue for\n  related bugs.\n\n# Assign `resolved` as the reason for locking. Set to `false` to disable\nsetLockReason: true\n\n# Limit to only `issues` or `pulls`\n# only: issues\n\n# Optionally, specify configuration settings just for `issues` or `pulls`\n# issues:\n#   exemptLabels:\n#     - help-wanted\n#   lockLabel: outdated\n\n# pulls:\n#   daysUntilLock: 30\n\n# Repository to extend settings from\n# _extends: repo\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '23 8 * * 5'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more:\n        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v2\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v2\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Build/release\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  release:\n    runs-on: ${{ matrix.os }}\n\n    # Platforms to build on/for\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v3\n\n      - name: Install Node.js, NPM and Yarn\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n\n      - name: Install Snapcraft\n        uses: samuelmeuli/action-snapcraft@v2\n        # Only install Snapcraft on Ubuntu\n        if: startsWith(matrix.os, 'ubuntu')\n        env:\n          # Snapcraft\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}\n\n      - name: Test Snapcraft\n        if: startsWith(matrix.os, 'ubuntu')\n        run: snapcraft --help\n\n      - name: Build/release Electron app\n        uses: motrixapp/action-electron-builder@v2\n        with:\n          build_script_name: 'build:github'\n          # GitHub token, automatically provided to the action\n          # (No need to define this secret in the repo settings)\n          github_token: ${{ secrets.github_token }}\n\n          # macOS code signing certificate\n          mac_certs: ${{ secrets.mac_certs }}\n          mac_certs_password: ${{ secrets.mac_certs_password }}\n\n          # If the commit is tagged with a version (e.g. \"v1.0.0\"),\n          # release the app after building\n          release: ${{ vars.skip_publish != 'true' }}\n        env:\n          # Snapcraft\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}\n          # macOS notarization\n          TEAM_ID: ${{ secrets.team_id }}\n          APPLE_ID: ${{ secrets.apple_id }}\n          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.apple_app_specific_password }}\n"
  },
  {
    "path": ".gitignore",
    "content": "!.gitkeep\n.DS_Store\n.env\n.idea/\n.vs/\n.vscode/\n*.log\nnode_modules/\nthumbs.db\n\n# npm package\n.npmrc\nnpm-debug.log.*\n\n# Eslint Cache\n.eslintcache*\n\n# electron builder\n*.provisionprofile\nbuild/*.plist\ndist/electron/*\ndist/web/*\n\n# release\nrelease/*\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\ndist: trusty\n\nlanguage: c\n\njobs:\n  include:\n  - os: osx\n    osx_image: xcode11.3\n  - os: linux\n    env: CC=clang CXX=clang++ npm_config_clang=1\n    compiler: clang\n\ncache:\n  directories:\n  - node_modules\n  - $HOME/.cache/electron\n  - $HOME/.cache/electron-builder\n\naddons:\n  apt:\n    packages:\n    - libgnome-keyring-dev\n    - icnsutils\n    - rpm\n\nbefore_install:\n- if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi\n\ninstall:\n- nvm install 12.14.1\n- source ~/.bashrc\n- npm install -g xvfb-maybe\n- npm install\n\nscript:\n- npm run release\n\nbefore_cache:\n  - rm -rf $HOME/.cache/electron-builder/wine\n\nbranches:\n  only:\n  - master\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at agalwood.net@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING-CN.md",
    "content": "# Motrix 贡献指南\n\n开始贡献之前，确保你已经理解了 [GitHub 的协作流程](https://guides.github.com/introduction/flow/)。\n\n## 🌍 翻译指南\n\n首先你要确定一个语言的英文简写作为 **locale**，如 en-US，这个 locale 值请严格参考 [Electron 的 Locales 文档](https://www.electronjs.org/docs/api/app#appgetlocale) 和 [Chromium 源代码](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc)。\n\nMotrix 的国际化分两部分：\n\n- Element UI\n- 菜单和主界面\n\n### Element UI\n\nElement UI 的国际化由 [Element 社区](http://element.eleme.io/#/en-US/component/i18n)提供，找到 **locale** 对应的语言包文件「两者 locale 命名可能不一致」，在 `src/shared/locales/all.js` 中引入，如\n\n```javascript\nimport eleLocaleEn from 'element-ui/lib/locale/lang/en'\nimport eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'\n```\n\n### 菜单和主界面\n\nMotrix 使用 i18next 作为翻译支持库，所以你可能需要简单了解一下它的[使用方法](https://www.i18next.com/overview/getting-started)。\n配置文件按照语言 (**locale**) 划分目录：`src/shared/locales`，如：`src/shared/locales/en-US` 和 `src/shared/locales/zh-CN`。\n\n目录里面有按业务模块划分的语言文件\n\n菜单模块经过重构之后，国际化已经打散到了以下文件里了，不再需要再复制 `src/main/menus` 里的配置。\n\n- about.js\n- app.js\n- edit.js\n- help.js\n- index.js\n- menu.js\n- preferences.js\n- subnav.js\n- task.js\n- window.js\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Motrix Contributing Guide\n\nBefore you start contributing, make sure you already understand [GitHub flow](https://guides.github.com/introduction/flow/).\n\n## 🌍 Translation Guide\n\nFirst you need to determine the English abbreviation of a language as **locale**, such as en-US, this locale value should strictly refer to the [Electron's Documentation](https://www.electronjs.org/docs/api/app#appgetlocale) and [Chromium Source Code](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc).\n\nThe internationalization of Motrix is divided into two parts:\n\n- Element UI\n- Menu & Main Interface\n\n### Element UI\n\nThe internationalization of Element UI is provided by the [Element community](http://element.eleme.io/#/en-US/component/i18n), then find the language pack file corresponding to **locale** (both locale naming may be inconsistent), which is import in `src/shared/locales/all.js`, such as\n\n```javascript\nimport eleLocaleEn from 'element-ui/lib/locale/lang/en'\nimport eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'\n```\n\n### Menu & Main Interface\n\nMotrix uses the [i18next](https://www.i18next.com/overview/getting-started) library for internationalization, so you need a quick look at how to use it.\nThe configuration files are divided by **locale**: `src/shared/locales`, such as `src/shared/locales/en-US` and `src/shared/locales/zh-CN`.\n\nThere are language files in the directory according to the business module.\n\nAfter the menu module is refactored, the internationalization of the menu has been dispersed into the following files, and there is no need to copy the configuration in `src/main/menus`.\n\n- about.js\n- app.js\n- edit.js\n- help.js\n- index.js\n- menu.js\n- preferences.js\n- subnav.js\n- task.js\n- window.js\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright 2018-present Dr_rOot\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README-CN.md",
    "content": "# Motrix\n\n<p>\n  <a href=\"https://motrix.app\">\n    <img src=\"./static/512x512.png\" width=\"256\" alt=\"Motrix App Icon\" />\n  </a>\n</p>\n\n## 一款全能的下载工具\n\n[![GitHub release](https://img.shields.io/github/v/release/agalwood/Motrix.svg)](https://github.com/agalwood/Motrix/releases) ![Build/release](https://github.com/agalwood/Motrix/workflows/Build/release/badge.svg) ![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)\n\n[English](./README.md) | 简体中文\n\n我是个兴趣使然的桌面应用开发者🤓，利用搬砖之余开发了 Motrix。\n\nMotrix 是一款全能的下载工具，支持下载 HTTP、FTP、BT、磁力链等资源。它的界面简洁易用，希望大家喜欢 👻。\n\n✈️ 去 [官网](https://motrix.app/zh-CN) 逛逛  |  📖 查看 [帮助手册](http://motrix.app/support/issues)\n\n## 💽 安装稳定版\n\n[GitHub](https://github.com/agalwood/Motrix/releases) 和 [官网](https://motrix.app/zh-CN) 提供了已经编译好的稳定版安装包，当然你也可以自己克隆代码编译打包。\n\n### Windows\n\n建议使用安装包（Motrix-Setup-x.y.z.exe）安装 Motrix 以确保完整的体验，例如关联 torrent 文件，捕获磁力链等。\n\n如果你在 Windows 是用包管理工具来管理应用，如 [Chocolatey](https://chocolatey.org)、[scoop](https://github.com/lukesampson/scoop)，你可以使用它们安装 Motrix。\n\n#### Chocolatey\n感谢 [@Yato](https://github.com/iYato) 持续维护着 [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) 包。要安装 Motrix，请从 `命令行` 或 `PowerShell` 中运行以下命令：\n\n```bash\n# 安装\nchoco install motrix\n\n# 升级\nchoco upgrade motrix\n```\n\n#### scoop\n如果你更喜欢便携版，你可以使用 [scoop](https://github.com/lukesampson/scoop)（需要 Windows 7+，天朝用户可能需要设置 Git 代理）安装最新便携版本的 Motrix。\n\n```bash\nscoop bucket add extras\nscoop install motrix\n```\n\n### macOS\n\nmacOS 用户可以使用 `brew` 安装 Motrix，感谢 [@Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。\n\n```bash\nbrew update && brew install motrix\n```\n\n#### 自动更新\nMotrix v1.8.0+ 版本更改了应用 BundleID ( `net.agalwood.Motrix` => `app.motrix.native` ), Motrix v1.6.11 的自动更新会因为签名不一致而失败。[Motrix 安装助手](https://github.com/motrixapp/motrix-install-assistant)将帮助您安装最新的 Motrix 应用程序。\n\n<p>\n  <a href=\"https://github.com/motrixapp/motrix-install-assistant\">\n    <img src=\"https://raw.githubusercontent.com/motrixapp/motrix-install-assistant/main/build/256x256.png\" width=\"192\" alt=\"Motrix Install Assistant Icon\" />\n  </a>\n</p>\n\n### Linux\n\n你可以下载 `AppImage` （适用于所有 Linux 发行版）或 `snap` 来安装 Motrix，更多 Linux 安装包格式请查看 [GitHub/release](https://github.com/agalwood/Motrix/releases) 。\n\nMotrix 在 Linux 中首次启动可能需要使用 `sudo` 运行，因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。\n\n如果你想自己通过编译源码来安装，请阅读 **编译打包** 部分。\n\n#### AppImage\n最新版的 Motrix AppImage 需要自己手动进执行桌面集成。请查看 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 的文档进行操作。\n\n> 桌面集成\n> electron-builder v21 之后，桌面集成不再是 AppImage 文件的一部分。\n> 推荐使用 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 集成 AppImage。\n\nDeepin 20 Beta 用户安装 Motrix 失败的问题，请按照以下方法处理：\n\n打开`终端`，黏贴运行如下命令之后再次安装 Motrix。\n```bash\nsudo apt --fix-broken install\n```\n\n#### Snap\nMotrix 已经上架 [Snapcraft](https://snapcraft.io/motrix) ，Ubuntu 用户推荐从 Snap 商店下载。\n\nv1.5.10 提示\n\n系统托盘可能无法正常显示指示器，导致退出应用程序不方便。\n请取消勾选 偏好设置——基本设置——隐藏应用程序菜单（仅限Windows和Linux），点击保存并应用。然后点击 \"文件 \"菜单中的 \"退出\"，退出应用程序。\n\n请更新到 v1.5.12 及以上版本，可以使用键盘组合快捷键 <kbd>Ctrl</kbd> + <kbd>q</kbd> 快速退出应用。\n\n#### AUR\n对于 Arch Linux 用户，可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix，感谢维护者 [@weearc](https://github.com/weearc)。\n\n运行以下命令进行安装：\n\n```bash\nyay -S motrix\n```\n\n#### Flatpak\n感谢 [@proletarius101](https://github.com/proletarius101) 的 [PR](https://github.com/flathub/flathub/pull/2334)，Motrix 已经上架 [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix)，喜欢 Flatpak 的 Linux 用户可以尝试。\n\n```bash\n# 安装\nflatpak install flathub net.agalwood.Motrix\n\n# 运行\nflatpak run net.agalwood.Motrix\n```\n\n## ✨ 特性\n\n- 🕹 简洁明了的图形操作界面\n- 🦄 支持BT和磁力链任务\n- ☑️ 支持选择性下载BT部分文件\n- 📡 每天自动更新 Tracker 服务器列表\n- 🔌 UPnP & NAT-PMP 端口映射\n- 🎛 最高支持 10 个任务同时下载\n- 🚀 单任务最高支持 64 线程下载\n- 🚥 设置上传/下载限速\n- 🕶 模拟用户代理UA\n- 🔔 下载完成后通知\n- 💻 支持触控栏快捷键 (Mac 专享)\n- 🤖 常驻系统托盘，操作更加便捷\n- 📟 系统托盘速度仪表显示实时速度 (Mac 专享)\n- 🌑 深色模式\n- 🗑 移除任务时可同时删除相关文件\n- 🌍 国际化，[查看已可选的语言](#-国际化)\n- 🛠 更多特性开发中\n\n## 🖥 应用界面\n\n![motrix-screenshot-task-cn.png](https://cdn.nlark.com/yuque/0/2020/png/129147/1589782239990-fecb9065-19ac-4c35-938b-0be45621ca3a.png)\n\n## ⌨️ 本地开发\n\n### 克隆代码\n\n```bash\ngit clone git@github.com:agalwood/Motrix.git\n```\n\n### 安装依赖\n\n```bash\ncd Motrix\nyarn\n```\n\n天朝大陆用户建议使用淘宝的 npm 源\n\n```bash\nyarn config set registry 'https://registry.npmmirror.com'\nnpm config set registry 'https://registry.npmmirror.com'\nexport ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/'\nexport SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'\n```\n\n> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again\n\n`Electron` 下载安装失败的问题，解决方式请参考 https://github.com/electron/electron/issues/8466#issuecomment-571425574\n\n### 开发模式\n\n```bash\nyarn run dev\n```\n\n### 编译打包\n\n```bash\nyarn run build\n```\n#### 编译 Apple Silicon 版本\n\n```bash\nyarn run build:applesilicon\n```\n完成之后可以在项目的 `release` 目录看到编译打包好的应用文件\n\n## 🛠 技术栈\n\n- [Electron](https://electronjs.org/)\n- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)\n- [Aria2](https://aria2.github.io/)\n\n## ☑️ TODO\n\n开发计划请移步 [Trello](https://trello.com/b/qNUzA0bv/motrix) 查看\n\n## 🤝 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)\n\n如果你有兴趣参与共同开发，欢迎 FORK 和 PR。\n\n## 🌍 国际化\n\n欢迎大家将 Motrix 翻译成更多的语言版本 🧐，开工之前请先阅读一下 [翻译指南](./CONTRIBUTING-CN.md#-翻译指南)。\n\n| Key   | Name                | Status       |\n|-------|:--------------------|:-------------|\n| ar    | Arabic              | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |\n| bg    | Българският език    | ✔️ [@null-none](https://github.com/null-none) |\n| ca    | Català              | ✔️ [@marcizhu](https://github.com/marcizhu) |\n| de    | Deutsch             | ✔️ [@Schloemicher](https://github.com/Schloemicher) |\n| el    | Ελληνικά            | ✔️ [@Likecinema](https://github.com/Likecinema) |\n| en-US | English             | ✔️           |\n| es    | Español             | ✔️ [@Chofito](https://github.com/Chofito)|\n| fa    | فارسی               | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |\n| fr    | Français            | ✔️ [@gpatarin](https://github.com/gpatarin) |\n| hu    | Hungarian           | ✔️ [@zalnaRs](https://github.com/zalnaRs) |\n| id    | Indonesia           | ✔️ [@aarestu](https://github.com/aarestu) |\n| it    | Italiano            | ✔️ [@blackcat-917](https://github.com/blackcat-917) |\n| ja    | 日本語               | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |\n| ko    | 한국어                | ✔️ [@KOZ39](https://github.com/KOZ39) |\n| nb    | Norsk Bokmål        | ✔️ [@rubjo](https://github.com/rubjo) |\n| nl    | Nederlands          | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |\n| pl    | Polski              | ✔️ [@KanarekLife](https://github.com/KanarekLife) |\n| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |\n| ro    | Română              | ✔️ [@alyn3d](https://github.com/alyn3d) |\n| ru    | Русский             | ✔️ [@bladeaweb](https://github.com/bladeaweb) |\n| th    | แบบไทย              | ✔️ [@nxanywhere](https://github.com/nxanywhere) |\n| tr    | Türkçe              | ✔️ [@abdullah](https://github.com/abdullah) |\n| uk    | Українська          | ✔️ [@bladeaweb](https://github.com/bladeaweb) |\n| vi    | Tiếng Việt          | ✔️ [@duythanhvn](https://github.com/duythanhvn) |\n| zh-CN | 简体中文             | ✔️           |\n| zh-TW | 繁體中文             | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |\n\n## 📜 开源许可\n\n基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。\n"
  },
  {
    "path": "README.md",
    "content": "# Motrix\n\n<p>\n  <a href=\"https://motrix.app\">\n    <img src=\"./static/512x512.png\" width=\"256\" alt=\"Motrix App Icon\" />\n  </a>\n</p>\n\n## A full-featured download manager\n\n[![GitHub release](https://img.shields.io/github/v/release/agalwood/Motrix.svg)](https://github.com/agalwood/Motrix/releases) ![Build/release](https://github.com/agalwood/Motrix/workflows/Build/release/badge.svg) ![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)\n\nEnglish | [简体中文](./README-CN.md)\n\nMotrix is a full-featured download manager that supports downloading HTTP, FTP, BitTorrent, Magnet, etc.\n\nMotrix has a clean and easy to use interface. I hope you will like it 👻.\n\n✈️ [Official Website](https://motrix.app) | 📖 [Manual](https://github.com/agalwood/Motrix/wiki)\n\n## 💽 Installation\n\nDownload from [GitHub Releases](https://github.com/agalwood/Motrix/releases) and install it.\n\n### Windows\n\nIt is recommended to install Motrix using the installation package (Motrix-Setup-x.y.z.exe) to ensure a complete experience, such as associating torrent files, capturing magnet links, etc.\n\nIf you use package management tools to manage applications on Windows, such as [Chocolatey](https://chocolatey.org), [scoop](https://github.com/lukesampson/scoop). You can use them to install Motrix.\n\n#### Chocolatey\nThanks to [@Yato](https://github.com/iYato) for continuing to maintain the [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) package. To install motrix, run the following command from the `command line` or from `PowerShell`:\n\n```bash\n# Install\nchoco install motrix\n\n# Upgrade\nchoco upgrade motrix\n```\n\n#### scoop\nIf you prefer the portable version, you can use [scoop](https://github.com/lukesampson/scoop) (need Windows 7+) to install Motrix.\n\n```bash\nscoop bucket add extras\nscoop install motrix\n```\n\n### macOS\n\nThe macOS users can install Motrix using `brew`, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [@Mitscherlich](https://github.com/Mitscherlich).\n\n```bash\nbrew update && brew install motrix\n```\n\n#### Auto Update\n\nSince Motrix v1.8.0 and later versions changed the App BundleID ( `net.agalwood.Motrix` => `app.motrix.native` ), the automatic update of Motrix v1.6.11 will fail. [Motrix Install Assistant](https://github.com/motrixapp/motrix-install-assistant) will help you install the latest Motrix application.\n\n<p>\n  <a href=\"https://github.com/motrixapp/motrix-install-assistant\">\n    <img src=\"https://raw.githubusercontent.com/motrixapp/motrix-install-assistant/main/build/256x256.png\" width=\"192\" alt=\"Motrix Install Assistant Icon\" />\n  </a>\n</p>\n\n### Linux\n\nYou can download the `AppImage` (for all Linux distributions) or `snap` to install Motrix, see [GitHub/release](https://github.com/agalwood/Motrix/releases) for more Linux installation package formats.\n\nMotrix may need to run with `sudo` for the first time in Linux because there is no permission to create the download session file (`/var/cache/aria2.session`).\n\nIf you want to build from source code, please read the **Build** section.\n\n#### AppImage\nThe latest version of Motrix AppImage requires you to manually perform desktop integration. Please check the documentation of [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) .\n\n> Desktop Integration\n> Since electron-builder 21 desktop integration is not a part of produced AppImage file.\n> [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) is the recommended way to integrate AppImages.\n\nDeepin 20 Beta users failed to install Motrix, please follow the steps below:\n\nOpen the `Terminal`, paste and run the following command to install Motrix again.\n\n```bash\nsudo apt --fix-broken install\n```\n\n#### Snap\nMotrix has been listed on [Snapcraft](https://snapcraft.io/motrix) , Ubuntu users recommend downloading from the Snap Store.\n\nTips for v1.5.10\n\nThe tray may not display the indicator normally, which makes it inconvenient to exit the application.\n\nPlease unchecked Preferences--Basic Settings--Hide App Menu (Windows & Linux Only), click Save & Apply. Then click \"Exit\" in the File menu to exit the application.\n\nPlease update to v1.5.12 and above, you can use the keyboard shortcut <kbd>Ctrl</kbd> + <kbd>q</kbd> to quickly exit the application.\n\n#### AUR\nFor Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [@weearc](https://github.com/weearc).\n\nRun the following command to install:\n\n```bash\nyay -S motrix\n```\n\n#### Flatpak\nThanks to the [PR](https://github.com/flathub/flathub/pull/2334) of [@proletarius101](https://github.com/proletarius101), Motrix has been listed [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix), Linux users who like the Flatpak can try it.\n\n```bash\n# Install\nflatpak install flathub net.agalwood.Motrix\n\n# Run\nflatpak run net.agalwood.Motrix\n```\n\n## ✨ Features\n\n- 🕹 Simple and clear user interface\n- 🦄 Supports BitTorrent & Magnet\n- ☑️ BitTorrent selective download\n- 📡 Update tracker list every day automatically\n- 🔌 UPnP & NAT-PMP Port Mapping\n- 🎛 Up to 10 concurrent download tasks\n- 🚀 Supports 64 threads in a single task\n- 🚥 Supports speed limit\n- 🕶 Mock User-Agent\n- 🔔 Download completed Notification\n- 💻 Ready for Touch Bar (Mac only)\n- 🤖 Resident system tray for quick operation\n- 📟 Tray speed meter displays real-time speed (Mac only)\n- 🌑 Dark mode\n- 🗑 Delete related files when removing tasks (optional)\n- 🌍 I18n, [View supported languages](#-internationalization).\n- 🛠 More features in development\n\n## 🖥 User Interface\n\n![motrix-screenshot-task-en.png](https://cdn.nlark.com/yuque/0/2020/png/129147/1589782238501-e7b39166-da58-4152-ae34-65a061cafa48.png)\n\n## ⌨️ Development\n\n### Clone Code\n\n```bash\ngit clone git@github.com:agalwood/Motrix.git\n```\n\n### Install Dependencies\n\n```bash\ncd Motrix\nyarn\n```\n\n> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again\n\n`Electron` failed to install correctly, please refer to https://github.com/electron/electron/issues/8466#issuecomment-571425574\n\n### Dev Mode\n\n```bash\nyarn run dev\n```\n\n### Build Release\n\n```bash\nyarn run build\n```\n#### Build for Apple Silicon\n\n```bash\nyarn run build:applesilicon\n```\n\nAfter building, the application will be found in the project's `release` directory.\n\n## 🛠 Technology Stack\n\n- [Electron](https://electronjs.org/)\n- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)\n- [Aria2](https://aria2.github.io/)\n\n## ☑️ TODO\n\nDevelopment Roadmap see: [Trello](https://trello.com/b/qNUzA0bv/motrix)\n\n## 🤝 Contribute [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)\n\nIf you are interested in participating in joint development, PR and Forks are welcome!\n\n## 🌍 Internationalization\n\nTranslations into versions for other languages are welcome 🧐! Please read the [translation guide](./CONTRIBUTING.md#-translation-guide) before starting translations.\n\n| Key   | Name                | Status       |\n|-------|:--------------------|:-------------|\n| ar    | Arabic              | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |\n| bg    | Българският език    | ✔️ [@null-none](https://github.com/null-none) |\n| ca    | Català              | ✔️ [@marcizhu](https://github.com/marcizhu) |\n| de    | Deutsch             | ✔️ [@Schloemicher](https://github.com/Schloemicher) |\n| el    | Ελληνικά            | ✔️ [@Likecinema](https://github.com/Likecinema) |\n| en-US | English             | ✔️           |\n| es    | Español             | ✔️ [@Chofito](https://github.com/Chofito)|\n| fa    | فارسی               | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |\n| fr    | Français            | ✔️ [@gpatarin](https://github.com/gpatarin) |\n| hu    | Hungarian           | ✔️ [@zalnaRs](https://github.com/zalnaRs) |\n| id    | Indonesia           | ✔️ [@aarestu](https://github.com/aarestu) |\n| it    | Italiano            | ✔️ [@blackcat-917](https://github.com/blackcat-917) |\n| ja    | 日本語               | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |\n| ko    | 한국어                | ✔️ [@KOZ39](https://github.com/KOZ39) |\n| nb    | Norsk Bokmål        | ✔️ [@rubjo](https://github.com/rubjo) |\n| nl    | Nederlands          | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |\n| pl    | Polski              | ✔️ [@KanarekLife](https://github.com/KanarekLife) |\n| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |\n| ro    | Română              | ✔️ [@alyn3d](https://github.com/alyn3d) |\n| ru    | Русский             | ✔️ [@bladeaweb](https://github.com/bladeaweb) |\n| th    | แบบไทย              | ✔️ [@nxanywhere](https://github.com/nxanywhere) |\n| tr    | Türkçe              | ✔️ [@abdullah](https://github.com/abdullah) |\n| uk    | Українська          | ✔️ [@bladeaweb](https://github.com/bladeaweb) |\n| vi    | Tiếng Việt          | ✔️ [@duythanhvn](https://github.com/duythanhvn) |\n| zh-CN | 简体中文             | ✔️           |\n| zh-TW | 繁體中文             | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |\n\n## 📜 License\n\n[MIT](https://opensource.org/licenses/MIT) Copyright (c) 2018-present Dr_rOot\n"
  },
  {
    "path": "app-update.yml",
    "content": "provider: generic\nurl: 'https://dl.motrix.app/releases/'\n"
  },
  {
    "path": "appveyor.yml",
    "content": "image: Visual Studio 2017\n\nplatform:\n  - x64\n\ncache:\n  - node_modules\n  - '%USERPROFILE%\\.electron'\n\ninit:\n  - git config --global core.autocrlf input\n\ninstall:\n  - ps: Install-Product node 12.14.1 x64\n  - git reset --hard HEAD\n  - npm install\n  - node --version\n\nbuild_script:\n  - npm run release\n\ntest: off\n\nbranches:\n  only:\n    - master\n"
  },
  {
    "path": "build/afterPackHook.js",
    "content": "//  Forked from https://github.com/samuelmeuli/mini-diary/blob/master/scripts/after-pack.js\n\n/**\n * Source: https://github.com/patrikx3/redis-ui/blob/master/src/build/after-pack.js\n *\n * Copyright (c) 2019 Patrik Laszlo / P3X / Corifeus and contributors.\n *\n * MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// TODO: Remove script once https://github.com/electron/electron/issues/17972 is solved by\n// `electron-builder`\n\nconst fs = require('node:fs')\nconst { spawn } = require('node:child_process')\nconst { chdir } = require('node:process')\n\nconst pkg = require('../package.json')\nconst binName = `${pkg.name}`.toLowerCase()\n\nconst exec = async function exec (cmd, args = []) {\n  const child = spawn(cmd, args, { shell: true })\n  redirectOutputFor(child)\n  await waitFor(child)\n}\n\nconst redirectOutputFor = child => {\n  const printStdout = data => {\n    process.stdout.write(data.toString())\n  }\n  const printStderr = data => {\n    process.stderr.write(data.toString())\n  }\n  child.stdout.on('data', printStdout)\n  child.stderr.on('data', printStderr)\n\n  child.once('close', () => {\n    child.stdout.off('data', printStdout)\n    child.stderr.off('data', printStderr)\n  })\n}\n\nconst waitFor = async function (child) {\n  return new Promise(resolve => {\n    child.once('close', () => resolve())\n  })\n}\n\nconst linuxTargets = [\n  'AppImage',\n  'deb',\n  'rpm',\n  'snap'\n]\n\nmodule.exports = async function (context) {\n  console.warn('after build; disable sandbox')\n  const isLinux = context.targets.find(\n    target => linuxTargets.includes(target)\n  )\n  if (!isLinux) {\n    return\n  }\n  const originalDir = process.cwd()\n  const dirname = context.appOutDir\n  chdir(dirname)\n\n  await exec('mv', [binName, binName + '.bin'])\n  const wrapperScript = `#!/bin/bash\n    \"\\${BASH_SOURCE%/*}\"/${binName}.bin \"$@\" --no-sandbox\n  `\n  fs.writeFileSync(binName, wrapperScript)\n  await exec('chmod', ['+x', binName])\n\n  chdir(originalDir)\n}\n"
  },
  {
    "path": "build/afterSignHook.js",
    "content": "require('dotenv').config()\nconst { join } = require('node:path')\nconst { notarize } = require('@electron/notarize')\nconst { appId } = require('../electron-builder.json')\n\nexports.default = async function (context) {\n  const { electronPlatformName, appOutDir } = context\n  if (electronPlatformName !== 'darwin') {\n    return\n  }\n\n  const skipNotarize = process.env.SKIP_NOTARIZE\n  if (skipNotarize === 'true') {\n    console.log('Skipping notarize')\n    return\n  }\n\n  const appBundleId = appId\n  const appName = context.packager.appInfo.productFilename\n  const appPath = join(appOutDir, `${appName}.app`)\n\n  try {\n    await notarize({\n      tool: 'notarytool',\n      appBundleId,\n      appPath,\n      teamId: process.env.TEAM_ID,\n      appleId: process.env.APPLE_ID,\n      appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD\n    })\n  } catch (error) {\n    console.error(error)\n  }\n\n  console.log(`Done notarizing ${appId}`)\n}\n"
  },
  {
    "path": "electron-builder.json",
    "content": "{\n    \"productName\": \"Motrix\",\n    \"appId\": \"app.motrix.native\",\n    \"afterPack\": \"./build/afterPackHook.js\",\n    \"afterSign\": \"./build/afterSignHook.js\",\n    \"fileAssociations\": [\n      {\n        \"ext\": \"torrent\",\n        \"mimeType\": \"application/x-bittorrent\",\n        \"name\": \"Torrent\",\n        \"role\": \"Viewer\"\n      }\n    ],\n    \"asar\": true,\n    \"directories\": {\n      \"output\": \"release\"\n    },\n    \"files\": [\n      \"dist/electron/**/*\"\n    ],\n    \"protocols\": [\n      {\n        \"name\": \"Motrix Protocol\",\n        \"schemes\": [\n          \"mo\",\n          \"motrix\"\n        ]\n      },\n      {\n        \"name\": \"Magnet Protocol\",\n        \"schemes\": [\n          \"magnet\"\n        ]\n      },\n      {\n        \"name\": \"Thunder Protocol\",\n        \"schemes\": [\n          \"thunder\"\n        ]\n      }\n    ],\n    \"dmg\": {\n      \"window\": {\n        \"width\": 540,\n        \"height\": 380\n      },\n      \"contents\": [\n        {\n          \"x\": 410,\n          \"y\": 230,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        },\n        {\n          \"x\": 130,\n          \"y\": 230,\n          \"type\": \"file\"\n        }\n      ]\n    },\n    \"mac\": {\n      \"target\": [\n        {\n          \"target\": \"dmg\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\",\n            \"universal\"\n          ]\n        },\n        {\n          \"target\": \"zip\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\",\n            \"universal\"\n          ]\n        }\n      ],\n      \"type\": \"development\",\n      \"darkModeSupport\": true,\n      \"hardenedRuntime\": false,\n      \"notarize\": false,\n      \"extraResources\": {\n        \"from\": \"./extra/darwin/${arch}/\",\n        \"to\": \"./\",\n        \"filter\": [\n          \"**/*\"\n        ]\n      },\n      \"category\": \"public.app-category.utilities\"\n    },\n    \"win\": {\n      \"target\": [\n        {\n          \"target\": \"nsis\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        },\n        {\n          \"target\": \"appx\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        },\n        {\n          \"target\": \"zip\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        },\n        {\n          \"target\": \"portable\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        }\n      ],\n      \"extraResources\": {\n        \"from\": \"./extra/win32/${arch}/\",\n        \"to\": \"./\",\n        \"filter\": [\n          \"**/*\"\n        ]\n      }\n    },\n    \"nsis\": {\n      \"artifactName\": \"${productName}-Setup-${version}.${ext}\",\n      \"oneClick\": false,\n      \"allowToChangeInstallationDirectory\": true\n    },\n    \"appx\": {\n      \"artifactName\": \"${productName}-${version}-${arch}.${ext}\",\n      \"applicationId\": \"app.motrix.native\",\n      \"identityName\": \"59744DrrOot.Motrix\",\n      \"publisher\": \"CN=5BB4961D-30D8-4993-9ADF-05E1E1F5A395\",\n      \"publisherDisplayName\": \"Dr_rOot\"\n    },\n    \"portable\": {\n      \"artifactName\": \"${productName}-${version}-${arch}.${ext}\"\n    },\n    \"linux\": {\n      \"category\": \"Network\",\n      \"mimeTypes\": [\n        \"application/x-bittorrent\",\n        \"x-scheme-handler/magnet\"\n      ],\n      \"target\": [\n        {\n          \"target\": \"AppImage\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\",\n            \"armv7l\"\n          ]\n        },\n        {\n          \"target\": \"deb\",\n          \"arch\": [\n            \"x64\",\n            \"arm64\",\n            \"armv7l\"\n          ]\n        },\n        {\n          \"target\": \"rpm\",\n          \"arch\": [\n            \"x64\"\n          ]\n        },\n        {\n          \"target\": \"snap\",\n          \"arch\": [\n            \"x64\"\n          ]\n        }\n      ],\n      \"extraResources\": {\n        \"from\": \"./extra/linux/${arch}/\",\n        \"to\": \"./\",\n        \"filter\": [\n          \"**/*\"\n        ]\n      }\n    },\n    \"publish\": [\n      {\n        \"provider\": \"generic\",\n        \"url\": \"https://dl.motrix.app/releases/\"\n      },\n      {\n        \"provider\": \"github\"\n      }\n    ]\n  }\n"
  },
  {
    "path": "extra/README.md",
    "content": "# aria2\n\nSource code: https://github.com/agalwood/aria2\n"
  },
  {
    "path": "extra/darwin/arm64/engine/aria2.conf",
    "content": "###############################\n# Motrix macOS Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=none\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/darwin/x64/engine/aria2.conf",
    "content": "###############################\n# Motrix macOS Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=none\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/linux/arm64/engine/aria2.conf",
    "content": "###############################\n# Motrix Linux Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=trunc\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/linux/armv7l/engine/aria2.conf",
    "content": "###############################\n# Motrix Linux Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=trunc\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/linux/x64/engine/aria2.conf",
    "content": "###############################\n# Motrix Linux Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=trunc\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/win32/ia32/engine/aria2.conf",
    "content": "###############################\n# Motrix Windows Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=none\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "extra/win32/x64/engine/aria2.conf",
    "content": "###############################\n# Motrix Windows Aria2 config file\n#\n# @see https://aria2.github.io/manual/en/html/aria2c.html\n#\n###############################\n\n\n################ RPC ################\n# Enable JSON-RPC/XML-RPC server.\nenable-rpc=true\n# Add Access-Control-Allow-Origin header field with value * to the RPC response.\nrpc-allow-origin-all=true\n# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.\nrpc-listen-all=true\n\n\n################ File system ################\n# Save a control file(*.aria2) every SEC seconds.\nauto-save-interval=10\n# Enable disk cache.\ndisk-cache=64M\n# Specify file allocation method.\nfile-allocation=falloc\n# No file allocation is made for files whose size is smaller than SIZE\nno-file-allocation-limit=64M\n# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.\nsave-session-interval=10\n\n\n################ Task ################\n# Exclude seed only downloads when counting concurrent active downloads\nbt-detach-seed-only=true\n# Verify the peer using certificates specified in --ca-certificate option.\ncheck-certificate=false\n# If aria2 receives \"file not found\" status from the remote HTTP/FTP servers NUM times\n# without getting a single byte, then force the download to fail.\nmax-file-not-found=10\n# Set number of tries.\nmax-tries=0\n# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.\nretry-wait=10\n# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.\nconnect-timeout=10\n# Set timeout in seconds.\ntimeout=10\n# aria2 does not split less than 2*SIZE byte range.\nmin-split-size=1M\n# Send Accept: deflate, gzip request header.\nhttp-accept-gzip=true\n# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.\nremote-time=true\n# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.\nsummary-interval=0\n# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.\ncontent-disposition-default-utf8=true\n\n\n################ BT Task ################\n# Enable Local Peer Discovery.\nbt-enable-lpd=true\n# Requires BitTorrent message payload encryption with arc4.\n# bt-force-encryption=true\n# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.\nbt-hash-check-seed=true\n# Specify the maximum number of peers per torrent.\nbt-max-peers=128\n# Try to download first and last pieces of each file first. This is useful for previewing files.\nbt-prioritize-piece=head\n# Removes the unselected files when download is completed in BitTorrent.\nbt-remove-unselected-file=true\n# Seed previously downloaded files without verifying piece hashes.\nbt-seed-unverified=false\n# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.\nbt-tracker-connect-timeout=10\n# Set timeout in seconds.\nbt-tracker-timeout=10\n# Set host and port as an entry point to IPv4 DHT network.\ndht-entry-point=dht.transmissionbt.com:6881\n# Set host and port as an entry point to IPv6 DHT network.\ndht-entry-point6=dht.transmissionbt.com:6881\n# Enable IPv4 DHT functionality. It also enables UDP tracker support.\nenable-dht=true\n# Enable IPv6 DHT functionality.\nenable-dht6=true\n# Enable Peer Exchange extension.\nenable-peer-exchange=true\n# Specify the string used during the bitorrent extended handshake for the peer's client version.\npeer-agent=Transmission/3.00\n# Specify the prefix of peer ID.\npeer-id-prefix=-TR3000-\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\n        \"./src/renderer/*\"\n      ],\n      \"@shared/*\": [\n        \"./src/shared/*\"\n      ]\n    }\n  },\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"Motrix\",\n  \"version\": \"1.8.19\",\n  \"description\": \"A full-featured download manager\",\n  \"homepage\": \"https://motrix.app\",\n  \"author\": {\n    \"name\": \"Dr_rOot\",\n    \"email\": \"agalwood.net@gmail.com\"\n  },\n  \"copyright\": \"Copyright© Dr_rOot\",\n  \"license\": \"MIT\",\n  \"main\": \"./dist/electron/main.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:agalwood/Motrix.git\"\n  },\n  \"scripts\": {\n    \"release\": \"npm run build --publish onTagOrDraft\",\n    \"build\": \"node .electron-vue/build.js && electron-builder\",\n    \"build:applesilicon\": \"node .electron-vue/build.js && electron-builder --arm64 --mac\",\n    \"build:github\": \"node .electron-vue/build.js\",\n    \"build:dir\": \"node .electron-vue/build.js && electron-builder --dir\",\n    \"build:clean\": \"cross-env BUILD_TARGET=clean node .electron-vue/build.js\",\n    \"build:web\": \"cross-env BUILD_TARGET=web node .electron-vue/build.js\",\n    \"dev\": \"node .electron-vue/dev-runner.js\",\n    \"dev:renderer\": \"webpack serve --node-env development --hot --color --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist\",\n    \"lint\": \"eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src\",\n    \"lint:fix\": \"eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src\",\n    \"pack\": \"npm run pack:main && npm run pack:renderer\",\n    \"pack:main\": \"webpack --node-env production --progress --color --config .electron-vue/webpack.main.config.js\",\n    \"pack:renderer\": \"webpack --node-env production --progress --color --config .electron-vue/webpack.renderer.config.js\",\n    \"postinstall\": \"electron-builder install-app-deps && npm run lint:fix\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  },\n  \"dependencies\": {\n    \"node-fetch\": \"^2.6.1\",\n    \"ws\": \"^8.13.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.21.8\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-transform-runtime\": \"^7.21.4\",\n    \"@babel/preset-env\": \"^7.21.5\",\n    \"@babel/register\": \"^7.21.0\",\n    \"@babel/runtime\": \"^7.21.5\",\n    \"@bany/curl-to-json\": \"^1.2.7\",\n    \"@electron/notarize\": \"^1.2.3\",\n    \"@electron/osx-sign\": \"^1.0.4\",\n    \"@electron/remote\": \"^2.0.9\",\n    \"@motrix/multispinner\": \"^0.2.4\",\n    \"@motrix/nat-api\": \"^0.3.4\",\n    \"@panter/vue-i18next\": \"^0.15.2\",\n    \"@vue/eslint-config-standard\": \"^6.1.0\",\n    \"ajv\": \"^8.12.0\",\n    \"axios\": \"^1.4.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"babel-loader\": \"^9.1.2\",\n    \"babel-plugin-component\": \"^1.1.1\",\n    \"bittorrent-peerid\": \"^1.3.6\",\n    \"blob-util\": \"^2.0.2\",\n    \"cfonts\": \"^3.2.0\",\n    \"chalk\": \"^4.1.2\",\n    \"copy-webpack-plugin\": \"^11.0.0\",\n    \"cross-env\": \"^7.0.3\",\n    \"css-loader\": \"^6.7.3\",\n    \"css-minimizer-webpack-plugin\": \"^5.0.0\",\n    \"del\": \"^6.1.1\",\n    \"electron\": \"^22.3.9\",\n    \"electron-builder\": \"^24.4.0\",\n    \"electron-devtools-installer\": \"^3.2.0\",\n    \"electron-is\": \"^3.0.0\",\n    \"electron-log\": \"^4.4.8\",\n    \"electron-store\": \"^8.1.0\",\n    \"electron-updater\": \"^6.1.0\",\n    \"element-ui\": \"^2.15.13\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-friendly-formatter\": \"^4.0.1\",\n    \"eslint-plugin-import\": \"^2.27.5\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^6.1.1\",\n    \"eslint-plugin-vue\": \"^9.12.0\",\n    \"eslint-webpack-plugin\": \"^4.0.1\",\n    \"file-loader\": \"^6.2.0\",\n    \"html-webpack-plugin\": \"^5.5.1\",\n    \"i18next\": \"^22.4.15\",\n    \"lodash\": \"^4.17.21\",\n    \"mini-css-extract-plugin\": \"2.7.5\",\n    \"node-loader\": \"^2.0.0\",\n    \"normalize.css\": \"^8.0.1\",\n    \"parse-torrent\": \"^9.1.5\",\n    \"randomatic\": \"^3.1.1\",\n    \"sass\": \"1.62.1\",\n    \"sass-loader\": \"^12.6.0\",\n    \"style-loader\": \"^3.3.2\",\n    \"terser-webpack-plugin\": \"^5.3.8\",\n    \"vue\": \"^2.7.14\",\n    \"vue-electron\": \"^1.0.6\",\n    \"vue-loader\": \"^15.10.1\",\n    \"vue-router\": \"^3.6.5\",\n    \"vue-selectable\": \"^0.5.0\",\n    \"vue-style-loader\": \"^4.1.3\",\n    \"vue-template-compiler\": \"^2.7.14\",\n    \"vuex\": \"^3.6.2\",\n    \"vuex-router-sync\": \"^5.0.0\",\n    \"webpack\": \"^5.82.1\",\n    \"webpack-cli\": \"^5.1.1\",\n    \"webpack-dev-server\": \"^4.15.0\",\n    \"webpack-hot-middleware\": \"^2.25.3\",\n    \"webpack-merge\": \"^5.8.0\",\n    \"worker-loader\": \"^3.0.8\"\n  }\n}\n"
  },
  {
    "path": "src/index.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Motrix</title>\n    <% if (htmlWebpackPlugin.options.nodeModules) { %>\n      <!-- Add `node_modules/` to global paths so `require` works properly in development -->\n      <script>\n        require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\\\/g, '\\\\\\\\') %>')\n      </script>\n    <% } %>\n  </head>\n\n  <style>\n    .skeleton-aside {\n      background-color: rgba(0, 0, 0, 0.8);\n      width: 78px;\n    }\n    .skeleton-subnav {\n      background-color: #f4f5f7;\n      width: 200px;\n    }\n    .skeleton-main {\n      background-color: #fff;\n    }\n\n    @media (prefers-color-scheme: dark) {\n      .skeleton-aside {\n        background-color: rgba(0, 0, 0, 0.9);\n      }\n      .skeleton-subnav {\n        background-color: #2D2D2D;\n      }\n      .skeleton-main {\n        background-color: #343434;\n      }\n    }\n  </style>\n\n  <body>\n    <div id=\"app\">\n      <div class=\"title-bar\"></div>\n      <section class=\"el-container\" id=\"container\">\n        <aside class=\"el-aside skeleton-aside hidden-sm-and-down\">\n        </aside>\n        <aside class=\"el-aside skeleton-subnav hidden-xs-only\">\n        </aside>\n        <section class=\"el-container skeleton-main\">\n        </section>\n      </section>\n    </div>\n    <!-- Set `__static` path to static files in production -->\n    <% if (!htmlWebpackPlugin.options.isBrowser && !htmlWebpackPlugin.options.isDev) { %>\n      <script>\n        window.__static = require('path').join(__dirname, '/static').replace(/\\\\/g, '\\\\\\\\')\n      </script>\n    <% } %>\n\n    <!-- webpack builds are automatically injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "src/main/Application.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { readFile, unlink } from 'node:fs'\nimport { extname, basename } from 'node:path'\nimport { app, shell, dialog, ipcMain } from 'electron'\nimport is from 'electron-is'\nimport { isEmpty, isEqual } from 'lodash'\n\nimport {\n  APP_RUN_MODE,\n  AUTO_SYNC_TRACKER_INTERVAL,\n  AUTO_CHECK_UPDATE_INTERVAL,\n  PROXY_SCOPES\n} from '@shared/constants'\nimport { checkIsNeedRun } from '@shared/utils'\nimport {\n  convertTrackerDataToComma,\n  fetchBtTrackerFromSource,\n  reduceTrackerString\n} from '@shared/utils/tracker'\nimport { showItemInFolder } from './utils'\nimport logger from './core/Logger'\nimport Context from './core/Context'\nimport ConfigManager from './core/ConfigManager'\nimport { setupLocaleManager } from './ui/Locale'\nimport Engine from './core/Engine'\nimport EngineClient from './core/EngineClient'\nimport UPnPManager from './core/UPnPManager'\nimport AutoLaunchManager from './core/AutoLaunchManager'\nimport UpdateManager from './core/UpdateManager'\nimport EnergyManager from './core/EnergyManager'\nimport ProtocolManager from './core/ProtocolManager'\nimport WindowManager from './ui/WindowManager'\nimport MenuManager from './ui/MenuManager'\nimport TouchBarManager from './ui/TouchBarManager'\nimport TrayManager from './ui/TrayManager'\nimport DockManager from './ui/DockManager'\nimport ThemeManager from './ui/ThemeManager'\n\nexport default class Application extends EventEmitter {\n  constructor () {\n    super()\n    this.isReady = false\n    this.init()\n  }\n\n  init () {\n    this.initContext()\n\n    this.initConfigManager()\n\n    this.setupLogger()\n\n    this.initLocaleManager()\n\n    this.setupApplicationMenu()\n\n    this.initWindowManager()\n\n    this.initUPnPManager()\n\n    this.startEngine()\n\n    this.initEngineClient()\n\n    this.initThemeManager()\n\n    this.initTrayManager()\n\n    this.initTouchBarManager()\n\n    this.initDockManager()\n\n    this.initAutoLaunchManager()\n\n    this.initEnergyManager()\n\n    this.initProtocolManager()\n\n    this.initUpdaterManager()\n\n    this.handleCommands()\n\n    this.handleEvents()\n\n    this.handleIpcMessages()\n\n    this.handleIpcInvokes()\n\n    this.emit('application:initialized')\n  }\n\n  initContext () {\n    this.context = new Context()\n  }\n\n  initConfigManager () {\n    this.configListeners = {}\n    this.configManager = new ConfigManager()\n  }\n\n  offConfigListeners () {\n    try {\n      Object.keys(this.configListeners).forEach((key) => {\n        this.configListeners[key]()\n      })\n    } catch (e) {\n      logger.warn('[Motrix] offConfigListeners===>', e)\n    }\n    this.configListeners = {}\n  }\n\n  setupLogger () {\n    const { userConfig } = this.configManager\n    const key = 'log-level'\n    const logLevel = userConfig.get(key)\n    logger.transports.file.level = logLevel\n\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      logger.transports.file.level = newValue\n    })\n  }\n\n  initLocaleManager () {\n    this.locale = this.configManager.getLocale()\n    this.localeManager = setupLocaleManager(this.locale)\n    this.i18n = this.localeManager.getI18n()\n  }\n\n  setupApplicationMenu () {\n    this.menuManager = new MenuManager()\n    this.menuManager.setup(this.locale)\n  }\n\n  adjustMenu () {\n    if (is.mas()) {\n      const visibleStates = {\n        'app.check-for-updates': false,\n        'task.new-bt-task': false\n      }\n      this.menuManager.updateMenuStates(visibleStates, null, null)\n      this.trayManager.updateMenuStates(visibleStates, null, null)\n    }\n  }\n\n  startEngine () {\n    const self = this\n\n    try {\n      this.engine = new Engine({\n        systemConfig: this.configManager.getSystemConfig(),\n        userConfig: this.configManager.getUserConfig()\n      })\n      this.engine.start()\n    } catch (err) {\n      const { message } = err\n      dialog.showMessageBox({\n        type: 'error',\n        title: this.i18n.t('app.system-error-title'),\n        message: this.i18n.t('app.system-error-message', { message })\n      }).then(_ => {\n        setTimeout(() => {\n          self.quit()\n        }, 100)\n      })\n    }\n  }\n\n  async stopEngine () {\n    logger.info('[Motrix] stopEngine===>')\n    try {\n      await this.engineClient.shutdown({ force: true })\n      logger.info('[Motrix] stopEngine.setImmediate===>')\n      setImmediate(() => {\n        this.engine.stop()\n      })\n    } catch (err) {\n      logger.warn('[Motrix] shutdown engine fail: ', err.message)\n    } finally {\n      // no finally\n    }\n  }\n\n  initEngineClient () {\n    const port = this.configManager.getSystemConfig('rpc-listen-port')\n    const secret = this.configManager.getSystemConfig('rpc-secret')\n    this.engineClient = new EngineClient({\n      port,\n      secret\n    })\n  }\n\n  initAutoLaunchManager () {\n    this.autoLaunchManager = new AutoLaunchManager()\n  }\n\n  initEnergyManager () {\n    this.energyManager = new EnergyManager()\n  }\n\n  initTrayManager () {\n    this.trayManager = new TrayManager({\n      theme: this.configManager.getUserConfig('tray-theme'),\n      systemTheme: this.themeManager.getSystemTheme(),\n      speedometer: this.configManager.getUserConfig('tray-speedometer'),\n      runMode: this.configManager.getUserConfig('run-mode')\n    })\n\n    this.watchTraySpeedometerEnabledChange()\n\n    this.trayManager.on('mouse-down', ({ focused }) => {\n      this.sendCommandToAll('application:update-tray-focused', { focused })\n    })\n\n    this.trayManager.on('mouse-up', ({ focused }) => {\n      this.sendCommandToAll('application:update-tray-focused', { focused })\n    })\n\n    this.trayManager.on('drop-files', (files = []) => {\n      this.handleFile(files[0])\n    })\n\n    this.trayManager.on('drop-text', (text) => {\n      this.handleProtocol(text)\n    })\n  }\n\n  watchTraySpeedometerEnabledChange () {\n    const { userConfig } = this.configManager\n    const key = 'tray-speedometer'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      this.trayManager.handleSpeedometerEnableChange(newValue)\n    })\n  }\n\n  initDockManager () {\n    this.dockManager = new DockManager({\n      runMode: this.configManager.getUserConfig('run-mode')\n    })\n  }\n\n  watchOpenAtLoginChange () {\n    const { userConfig } = this.configManager\n    const key = 'open-at-login'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      if (is.linux()) {\n        return\n      }\n\n      if (newValue) {\n        this.autoLaunchManager.enable()\n      } else {\n        this.autoLaunchManager.disable()\n      }\n    })\n  }\n\n  watchProtocolsChange () {\n    const { userConfig } = this.configManager\n    const key = 'protocols'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n\n      if (!newValue || isEqual(newValue, oldValue)) {\n        return\n      }\n\n      logger.info('[Motrix] setup protocols client:', newValue)\n      this.protocolManager.setup(newValue)\n    })\n  }\n\n  watchRunModeChange () {\n    const { userConfig } = this.configManager\n    const key = 'run-mode'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      this.trayManager.handleRunModeChange(newValue)\n\n      if (newValue !== APP_RUN_MODE.TRAY) {\n        this.dockManager.show()\n      } else {\n        this.dockManager.hide()\n        // Hiding the dock icon will trigger the entire app to hide.\n        this.show()\n      }\n    })\n  }\n\n  watchProxyChange () {\n    const { userConfig } = this.configManager\n    const key = 'proxy'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      this.updateManager.setupProxy(newValue)\n\n      const { enable, server, bypass, scope = [] } = newValue\n      const system = enable && server && scope.includes(PROXY_SCOPES.DOWNLOAD)\n        ? {\n          'all-proxy': server,\n          'no-proxy': bypass\n        }\n        : {}\n      this.configManager.setSystemConfig(system)\n      this.engineClient.call('changeGlobalOption', system)\n    })\n  }\n\n  watchLocaleChange () {\n    const { userConfig } = this.configManager\n    const key = 'locale'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      this.localeManager.changeLanguageByLocale(newValue)\n        .then(() => {\n          this.menuManager.handleLocaleChange(newValue)\n          this.trayManager.handleLocaleChange(newValue)\n        })\n      this.sendCommandToAll('application:update-locale', { locale: newValue })\n    })\n  }\n\n  watchThemeChange () {\n    const { userConfig } = this.configManager\n    const key = 'theme'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n      this.themeManager.updateSystemTheme(newValue)\n      this.sendCommandToAll('application:update-theme', { theme: newValue })\n    })\n  }\n\n  watchShowProgressBarChange () {\n    const { userConfig } = this.configManager\n    const key = 'show-progress-bar'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)\n\n      if (newValue) {\n        this.bindProgressChange()\n      } else {\n        this.unbindProgressChange()\n      }\n    })\n  }\n\n  initUPnPManager () {\n    this.upnp = new UPnPManager()\n\n    this.watchUPnPEnabledChange()\n\n    this.watchUPnPPortsChange()\n\n    const enabled = this.configManager.getUserConfig('enable-upnp')\n    if (!enabled) {\n      return\n    }\n\n    this.startUPnPMapping()\n  }\n\n  async startUPnPMapping () {\n    const btPort = this.configManager.getSystemConfig('listen-port')\n    const dhtPort = this.configManager.getSystemConfig('dht-listen-port')\n\n    const promises = [\n      this.upnp.map(btPort),\n      this.upnp.map(dhtPort)\n    ]\n    try {\n      await Promise.allSettled(promises)\n    } catch (e) {\n      logger.warn('[Motrix] start UPnP mapping fail', e.message)\n    }\n  }\n\n  async stopUPnPMapping () {\n    const btPort = this.configManager.getSystemConfig('listen-port')\n    const dhtPort = this.configManager.getSystemConfig('dht-listen-port')\n\n    const promises = [\n      this.upnp.unmap(btPort),\n      this.upnp.unmap(dhtPort)\n    ]\n    try {\n      await Promise.allSettled(promises)\n    } catch (e) {\n      logger.warn('[Motrix] stop UPnP mapping fail', e)\n    }\n  }\n\n  watchUPnPPortsChange () {\n    const { systemConfig } = this.configManager\n    const watchKeys = ['listen-port', 'dht-listen-port']\n\n    watchKeys.forEach((key) => {\n      this.configListeners[key] = systemConfig.onDidChange(key, async (newValue, oldValue) => {\n        logger.info('[Motrix] detected port change event:', key, newValue, oldValue)\n        const enable = this.configManager.getUserConfig('enable-upnp')\n        if (!enable) {\n          return\n        }\n\n        const promises = [\n          this.upnp.unmap(oldValue),\n          this.upnp.map(newValue)\n        ]\n        try {\n          await Promise.allSettled(promises)\n        } catch (e) {\n          logger.info('[Motrix] change UPnP port mapping failed:', e)\n        }\n      })\n    })\n  }\n\n  watchUPnPEnabledChange () {\n    const { userConfig } = this.configManager\n    const key = 'enable-upnp'\n    this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {\n      logger.info('[Motrix] detected enable-upnp value change event:', newValue, oldValue)\n      if (newValue) {\n        this.startUPnPMapping()\n      } else {\n        await this.stopUPnPMapping()\n        this.upnp.closeClient()\n      }\n    })\n  }\n\n  async shutdownUPnPManager () {\n    const enable = this.configManager.getUserConfig('enable-upnp')\n    if (enable) {\n      await this.stopUPnPMapping()\n    }\n\n    this.upnp.closeClient()\n  }\n\n  syncTrackers (source, proxy) {\n    if (isEmpty(source)) {\n      return\n    }\n\n    setTimeout(() => {\n      fetchBtTrackerFromSource(source, proxy).then((data) => {\n        logger.warn('[Motrix] auto sync tracker data:', data)\n        if (!data || data.length === 0) {\n          return\n        }\n\n        let tracker = convertTrackerDataToComma(data)\n        tracker = reduceTrackerString(tracker)\n        this.savePreference({\n          system: {\n            'bt-tracker': tracker\n          },\n          user: {\n            'last-sync-tracker-time': Date.now()\n          }\n        })\n      }).catch((err) => {\n        logger.warn('[Motrix] auto sync tracker failed:', err.message)\n      })\n    }, 500)\n  }\n\n  autoSyncTrackers () {\n    const enable = this.configManager.getUserConfig('auto-sync-tracker')\n    const lastTime = this.configManager.getUserConfig('last-sync-tracker-time')\n    const result = checkIsNeedRun(enable, lastTime, AUTO_SYNC_TRACKER_INTERVAL)\n    logger.info('[Motrix] auto sync tracker checkIsNeedRun:', result)\n    if (!result) {\n      return\n    }\n\n    const source = this.configManager.getUserConfig('tracker-source')\n    const proxy = this.configManager.getUserConfig('proxy', { enable: false })\n\n    this.syncTrackers(source, proxy)\n  }\n\n  autoResumeTask () {\n    const enabled = this.configManager.getUserConfig('resume-all-when-app-launched')\n    if (!enabled) {\n      return\n    }\n\n    this.engineClient.call('unpauseAll')\n  }\n\n  initWindowManager () {\n    this.windowManager = new WindowManager({\n      userConfig: this.configManager.getUserConfig()\n    })\n\n    this.windowManager.on('window-resized', (data) => {\n      this.storeWindowState(data)\n    })\n\n    this.windowManager.on('window-moved', (data) => {\n      this.storeWindowState(data)\n    })\n\n    this.windowManager.on('window-closed', (data) => {\n      this.storeWindowState(data)\n    })\n\n    this.windowManager.on('enter-full-screen', (window) => {\n      this.dockManager.show()\n    })\n\n    this.windowManager.on('leave-full-screen', (window) => {\n      const mode = this.configManager.getUserConfig('run-mode')\n      if (mode === APP_RUN_MODE.TRAY) {\n        this.dockManager.hide()\n      }\n    })\n  }\n\n  storeWindowState (data = {}) {\n    const enabled = this.configManager.getUserConfig('keep-window-state')\n    if (!enabled) {\n      return\n    }\n\n    const state = this.configManager.getUserConfig('window-state', {})\n    const { page, bounds } = data\n    const newState = {\n      ...state,\n      [page]: bounds\n    }\n    this.configManager.setUserConfig('window-state', newState)\n  }\n\n  start (page, options = {}) {\n    const win = this.showPage(page, options)\n\n    win.once('ready-to-show', () => {\n      this.isReady = true\n      this.emit('ready')\n    })\n\n    if (is.macOS()) {\n      this.touchBarManager.setup(page, win)\n    }\n  }\n\n  showPage (page, options = {}) {\n    const { openedAtLogin } = options\n    const autoHideWindow = this.configManager.getUserConfig('auto-hide-window')\n    return this.windowManager.openWindow(page, {\n      hidden: openedAtLogin || autoHideWindow\n    })\n  }\n\n  show (page = 'index') {\n    this.windowManager.showWindow(page)\n  }\n\n  hide (page) {\n    if (page) {\n      this.windowManager.hideWindow(page)\n    } else {\n      this.windowManager.hideAllWindow()\n    }\n  }\n\n  toggle (page = 'index') {\n    this.windowManager.toggleWindow(page)\n  }\n\n  closePage (page) {\n    this.windowManager.destroyWindow(page)\n  }\n\n  stop () {\n    try {\n      const promises = [\n        this.stopEngine(),\n        this.shutdownUPnPManager(),\n        this.energyManager.stopPowerSaveBlocker(),\n        this.trayManager.destroy()\n      ]\n\n      return promises\n    } catch (err) {\n      logger.warn('[Motrix] stop error: ', err.message)\n    }\n  }\n\n  async stopAllSettled () {\n    await Promise.allSettled(this.stop())\n  }\n\n  async quit () {\n    await this.stopAllSettled()\n    app.exit()\n  }\n\n  sendCommand (command, ...args) {\n    if (!this.emit(command, ...args)) {\n      const window = this.windowManager.getFocusedWindow()\n      if (window) {\n        this.windowManager.sendCommandTo(window, command, ...args)\n      }\n    }\n  }\n\n  sendCommandToAll (command, ...args) {\n    if (!this.emit(command, ...args)) {\n      this.windowManager.getWindowList().forEach(window => {\n        this.windowManager.sendCommandTo(window, command, ...args)\n      })\n    }\n  }\n\n  sendMessageToAll (channel, ...args) {\n    this.windowManager.getWindowList().forEach(window => {\n      this.windowManager.sendMessageTo(window, channel, ...args)\n    })\n  }\n\n  initThemeManager () {\n    this.themeManager = new ThemeManager()\n    this.themeManager.on('system-theme-change', (theme) => {\n      this.trayManager.handleSystemThemeChange(theme)\n      this.sendCommandToAll('application:update-system-theme', { theme })\n    })\n  }\n\n  initTouchBarManager () {\n    if (!is.macOS()) {\n      return\n    }\n\n    this.touchBarManager = new TouchBarManager()\n  }\n\n  initProtocolManager () {\n    const protocols = this.configManager.getUserConfig('protocols', {})\n    this.protocolManager = new ProtocolManager({\n      protocols\n    })\n  }\n\n  handleProtocol (url) {\n    this.show()\n\n    this.protocolManager.handle(url)\n  }\n\n  handleFile (filePath) {\n    if (!filePath) {\n      return\n    }\n\n    if (extname(filePath).toLowerCase() !== '.torrent') {\n      return\n    }\n\n    this.show()\n\n    const name = basename(filePath)\n    readFile(filePath, (err, data) => {\n      if (err) {\n        logger.warn(`[Motrix] read file error: ${filePath}`, err.message)\n        return\n      }\n      const dataURL = Buffer.from(data).toString('base64')\n      this.sendCommandToAll('application:new-bt-task-with-file', {\n        name,\n        dataURL\n      })\n    })\n  }\n\n  initUpdaterManager () {\n    if (is.mas()) {\n      return\n    }\n\n    const enabled = this.configManager.getUserConfig('auto-check-update')\n    const proxy = this.configManager.getSystemConfig('all-proxy')\n    const lastTime = this.configManager.getUserConfig('last-check-update-time')\n    const autoCheck = checkIsNeedRun(enabled, lastTime, AUTO_CHECK_UPDATE_INTERVAL)\n    this.updateManager = new UpdateManager({\n      autoCheck,\n      proxy\n    })\n    this.handleUpdaterEvents()\n  }\n\n  handleUpdaterEvents () {\n    this.updateManager.on('checking', (event) => {\n      this.menuManager.updateMenuItemEnabledState('app.check-for-updates', false)\n      this.trayManager.updateMenuItemEnabledState('app.check-for-updates', false)\n      this.configManager.setUserConfig('last-check-update-time', Date.now())\n    })\n\n    this.updateManager.on('download-progress', (event) => {\n      const win = this.windowManager.getWindow('index')\n      win.setProgressBar(event.percent / 100)\n    })\n\n    this.updateManager.on('update-not-available', (event) => {\n      this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)\n    })\n\n    this.updateManager.on('update-downloaded', (event) => {\n      this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      const win = this.windowManager.getWindow('index')\n      win.setProgressBar(1)\n    })\n\n    this.updateManager.on('update-cancelled', (event) => {\n      this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      const win = this.windowManager.getWindow('index')\n      win.setProgressBar(-1)\n    })\n\n    this.updateManager.on('will-updated', async (event) => {\n      this.windowManager.setWillQuit(true)\n      await this.stopAllSettled()\n    })\n\n    this.updateManager.on('update-error', (event) => {\n      this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)\n      this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)\n    })\n  }\n\n  async relaunch () {\n    await this.stopAllSettled()\n    app.relaunch()\n    app.exit()\n  }\n\n  async resetSession () {\n    await this.stopEngine()\n\n    app.clearRecentDocuments()\n\n    const sessionPath = this.context.get('session-path')\n    setTimeout(() => {\n      unlink(sessionPath, (err) => {\n        logger.info('[Motrix] Removed the download seesion file:', err)\n      })\n\n      this.engine.start()\n    }, 3000)\n  }\n\n  savePreference (config = {}) {\n    logger.info('[Motrix] save preference:', config)\n    const { system, user } = config\n    if (!isEmpty(system)) {\n      console.info('[Motrix] main save system config: ', system)\n      this.configManager.setSystemConfig(system)\n      this.engineClient.changeGlobalOption(system)\n    }\n\n    if (!isEmpty(user)) {\n      console.info('[Motrix] main save user config: ', user)\n      this.configManager.setUserConfig(user)\n    }\n  }\n\n  handleCommands () {\n    this.on('application:save-preference', this.savePreference)\n\n    this.on('application:update-tray', (tray) => {\n      this.trayManager.updateTrayByImage(tray)\n    })\n\n    this.on('application:relaunch', () => {\n      this.relaunch()\n    })\n\n    this.on('application:quit', () => {\n      this.quit()\n    })\n\n    this.on('application:show', ({ page }) => {\n      this.show(page)\n    })\n\n    this.on('application:hide', ({ page }) => {\n      this.hide(page)\n    })\n\n    this.on('application:reset-session', () => this.resetSession())\n\n    this.on('application:factory-reset', () => {\n      this.offConfigListeners()\n      this.configManager.reset()\n      this.relaunch()\n    })\n\n    this.on('application:check-for-updates', () => {\n      this.updateManager.check()\n    })\n\n    this.on('application:change-theme', (theme) => {\n      this.themeManager.updateSystemTheme(theme)\n      this.sendCommandToAll('application:update-theme', { theme })\n    })\n\n    this.on('application:change-locale', (locale) => {\n      this.localeManager.changeLanguageByLocale(locale)\n        .then(() => {\n          this.menuManager.handleLocaleChange(locale)\n          this.trayManager.handleLocaleChange(locale)\n        })\n    })\n\n    this.on('application:toggle-dock', (visible) => {\n      if (visible) {\n        this.dockManager.show()\n      } else {\n        this.dockManager.hide()\n        // Hiding the dock icon will trigger the entire app to hide.\n        this.show()\n      }\n    })\n\n    this.on('application:auto-hide-window', (hide) => {\n      if (hide) {\n        this.windowManager.handleWindowBlur()\n      } else {\n        this.windowManager.unbindWindowBlur()\n      }\n    })\n\n    this.on('application:change-menu-states', (visibleStates, enabledStates, checkedStates) => {\n      this.menuManager.updateMenuStates(visibleStates, enabledStates, checkedStates)\n      this.trayManager.updateMenuStates(visibleStates, enabledStates, checkedStates)\n    })\n\n    this.on('application:open-file', (event) => {\n      dialog.showOpenDialog({\n        properties: ['openFile'],\n        filters: [\n          {\n            name: 'Torrent',\n            extensions: ['torrent']\n          }\n        ]\n      }).then(({ canceled, filePaths }) => {\n        if (canceled || filePaths.length === 0) {\n          return\n        }\n\n        const [filePath] = filePaths\n        this.handleFile(filePath)\n      })\n    })\n\n    this.on('application:clear-recent-tasks', () => {\n      app.clearRecentDocuments()\n    })\n\n    this.on('application:setup-protocols-client', (protocols) => {\n      if (is.dev() || is.mas() || !protocols) {\n        return\n      }\n      logger.info('[Motrix] setup protocols client:', protocols)\n      this.protocolManager.setup(protocols)\n    })\n\n    this.on('application:open-external', (url) => {\n      this.openExternal(url)\n    })\n\n    this.on('application:reveal-in-folder', (data) => {\n      const { gid, path } = data\n      logger.info('[Motrix] application:reveal-in-folder===>', path)\n      if (path) {\n        showItemInFolder(path)\n      }\n      if (gid) {\n        this.sendCommandToAll('application:show-task-detail', { gid })\n      }\n    })\n\n    this.on('help:official-website', () => {\n      const url = 'https://motrix.app/'\n      this.openExternal(url)\n    })\n\n    this.on('help:manual', () => {\n      const url = 'https://motrix.app/manual'\n      this.openExternal(url)\n    })\n\n    this.on('help:release-notes', () => {\n      const url = 'https://motrix.app/release'\n      this.openExternal(url)\n    })\n\n    this.on('help:report-problem', () => {\n      const url = 'https://motrix.app/report'\n      this.openExternal(url)\n    })\n  }\n\n  openExternal (url) {\n    if (!url) {\n      return\n    }\n\n    shell.openExternal(url)\n  }\n\n  handleConfigChange (configName) {\n    this.sendCommandToAll('application:update-preference-config', { configName })\n  }\n\n  handleEvents () {\n    this.once('application:initialized', () => {\n      this.autoSyncTrackers()\n\n      this.autoResumeTask()\n\n      this.adjustMenu()\n    })\n\n    this.configManager.userConfig.onDidAnyChange(() => this.handleConfigChange('user'))\n    this.configManager.systemConfig.onDidAnyChange(() => this.handleConfigChange('system'))\n\n    this.watchOpenAtLoginChange()\n    this.watchProtocolsChange()\n    this.watchRunModeChange()\n    this.watchShowProgressBarChange()\n    this.watchProxyChange()\n    this.watchLocaleChange()\n    this.watchThemeChange()\n\n    this.on('download-status-change', (downloading) => {\n      this.trayManager.handleDownloadStatusChange(downloading)\n      if (downloading) {\n        this.energyManager.startPowerSaveBlocker()\n      } else {\n        this.energyManager.stopPowerSaveBlocker()\n      }\n    })\n\n    this.on('speed-change', (speed) => {\n      this.dockManager.handleSpeedChange(speed)\n      this.trayManager.handleSpeedChange(speed)\n    })\n\n    this.on('task-download-complete', (task, path) => {\n      this.dockManager.openDock(path)\n\n      if (is.linux()) {\n        return\n      }\n      app.addRecentDocument(path)\n    })\n\n    if (this.configManager.userConfig.get('show-progress-bar')) {\n      this.bindProgressChange()\n    }\n  }\n\n  handleProgressChange (progress) {\n    if (this.updateManager.isChecking) {\n      return\n    }\n    if (!is.windows() && progress === 2) {\n      progress = 0\n    }\n    this.windowManager.getWindow('index').setProgressBar(progress)\n  }\n\n  bindProgressChange () {\n    if (this.listeners('progress-change').length > 0) {\n      return\n    }\n\n    this.on('progress-change', this.handleProgressChange)\n  }\n\n  unbindProgressChange () {\n    if (this.listeners('progress-change').length === 0) {\n      return\n    }\n\n    this.off('progress-change', this.handleProgressChange)\n    this.windowManager.getWindow('index').setProgressBar(-1)\n  }\n\n  handleIpcMessages () {\n    ipcMain.on('command', (event, command, ...args) => {\n      logger.log('[Motrix] ipc receive command', command, ...args)\n      this.emit(command, ...args)\n    })\n\n    ipcMain.on('event', (event, eventName, ...args) => {\n      logger.log('[Motrix] ipc receive event', eventName, ...args)\n      this.emit(eventName, ...args)\n    })\n  }\n\n  handleIpcInvokes () {\n    ipcMain.handle('get-app-config', async () => {\n      const systemConfig = this.configManager.getSystemConfig()\n      const userConfig = this.configManager.getUserConfig()\n      const context = this.context.get()\n\n      const result = {\n        ...systemConfig,\n        ...userConfig,\n        ...context\n      }\n      return result\n    })\n  }\n}\n"
  },
  {
    "path": "src/main/Launcher.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { app } from 'electron'\nimport is from 'electron-is'\n\nimport ExceptionHandler from './core/ExceptionHandler'\nimport logger from './core/Logger'\nimport Application from './Application'\nimport {\n  splitArgv,\n  parseArgvAsUrl,\n  parseArgvAsFile\n} from './utils'\nimport { EMPTY_STRING } from '@shared/constants'\n\nexport default class Launcher extends EventEmitter {\n  constructor () {\n    super()\n    this.url = EMPTY_STRING\n    this.file = EMPTY_STRING\n\n    this.makeSingleInstance(() => {\n      this.init()\n    })\n  }\n\n  makeSingleInstance (callback) {\n    // Mac App Store Sandboxed App not support requestSingleInstanceLock\n    if (is.mas()) {\n      callback && callback()\n      return\n    }\n\n    const gotSingleLock = app.requestSingleInstanceLock()\n\n    if (!gotSingleLock) {\n      app.quit()\n    } else {\n      app.on('second-instance', (event, argv, workingDirectory) => {\n        global.application.showPage('index')\n        if (!is.macOS() && argv.length > 1) {\n          this.handleAppLaunchArgv(argv)\n        }\n      })\n\n      callback && callback()\n    }\n  }\n\n  init () {\n    this.exceptionHandler = new ExceptionHandler()\n\n    this.openedAtLogin = is.macOS()\n      ? app.getLoginItemSettings().wasOpenedAtLogin\n      : false\n\n    if (process.argv.length > 1) {\n      this.handleAppLaunchArgv(process.argv)\n    }\n\n    logger.info('[Motrix] openedAtLogin:', this.openedAtLogin)\n\n    this.handleAppEvents()\n  }\n\n  handleAppEvents () {\n    this.handleRendererRemote()\n    this.handleOpenUrl()\n    this.handleOpenFile()\n\n    this.handelAppReady()\n    this.handleAppWillQuit()\n  }\n\n  handleRendererRemote () {\n    app.on('browser-window-created', (_, window) => {\n      require('@electron/remote/main').enable(window.webContents)\n    })\n  }\n\n  /**\n   * handleOpenUrl\n   * Event 'open-url' macOS only\n   * \"name\": \"Motrix Protocol\",\n   * \"schemes\": [\"mo\", \"motrix\"]\n   */\n  handleOpenUrl () {\n    if (is.mas() || !is.macOS()) {\n      return\n    }\n    app.on('open-url', (event, url) => {\n      logger.info(`[Motrix] open-url: ${url}`)\n      event.preventDefault()\n      this.url = url\n      this.sendUrlToApplication()\n    })\n  }\n\n  /**\n   * handleOpenFile\n   * Event 'open-file' macOS only\n   * handle open torrent file\n   */\n  handleOpenFile () {\n    if (!is.macOS()) {\n      return\n    }\n    app.on('open-file', (event, path) => {\n      logger.info(`[Motrix] open-file: ${path}`)\n      event.preventDefault()\n      this.file = path\n      this.sendFileToApplication()\n    })\n  }\n\n  /**\n   * handleAppLaunchArgv\n   * For Windows, Linux\n   * @param {array} argv\n   */\n  handleAppLaunchArgv (argv) {\n    logger.info('[Motrix] handleAppLaunchArgv:', argv)\n\n    // args: array, extra: map\n    const { args, extra } = splitArgv(argv)\n    logger.info('[Motrix] split argv args:', args)\n    logger.info('[Motrix] split argv extra:', extra)\n    if (extra['--opened-at-login'] === '1') {\n      this.openedAtLogin = true\n    }\n\n    const file = parseArgvAsFile(args)\n    if (file) {\n      this.file = file\n      this.sendFileToApplication()\n    }\n\n    const url = parseArgvAsUrl(args)\n    if (url) {\n      this.url = url\n      this.sendUrlToApplication()\n    }\n  }\n\n  sendUrlToApplication () {\n    if (this.url && global.application && global.application.isReady) {\n      global.application.handleProtocol(this.url)\n      this.url = EMPTY_STRING\n    }\n  }\n\n  sendFileToApplication () {\n    if (this.file && global.application && global.application.isReady) {\n      global.application.handleFile(this.file)\n      this.file = EMPTY_STRING\n    }\n  }\n\n  handelAppReady () {\n    app.on('ready', () => {\n      global.application = new Application()\n\n      const { openedAtLogin } = this\n      global.application.start('index', {\n        openedAtLogin\n      })\n\n      global.application.on('ready', () => {\n        this.sendUrlToApplication()\n\n        this.sendFileToApplication()\n      })\n    })\n\n    app.on('activate', () => {\n      if (global.application) {\n        logger.info('[Motrix] activate')\n        global.application.showPage('index')\n      }\n    })\n  }\n\n  handleAppWillQuit () {\n    app.on('will-quit', () => {\n      logger.info('[Motrix] will-quit')\n      if (global.application) {\n        logger.info('[Motrix] will-quit.application.stop')\n        global.application.stop()\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "src/main/configs/engine.js",
    "content": "export const engineBinMap = {\n  darwin: 'aria2c',\n  win32: 'aria2c.exe',\n  linux: 'aria2c'\n}\n\nexport const engineArchMap = {\n  darwin: {\n    x64: 'x64',\n    arm64: 'arm64'\n  },\n  win32: {\n    ia32: 'ia32',\n    x64: 'x64',\n    arm64: 'x64'\n  },\n  linux: {\n    x64: 'x64',\n    arm: 'armv7l',\n    arm64: 'arm64'\n  }\n}\n"
  },
  {
    "path": "src/main/configs/page.js",
    "content": "import is from 'electron-is'\n\nexport default {\n  index: {\n    attrs: {\n      title: 'Motrix',\n      width: 1024,\n      height: 768,\n      minWidth: 478,\n      minHeight: 420,\n      transparent: is.macOS()\n    },\n    bindCloseToHide: true,\n    openDevTools: is.dev(),\n    url: is.dev() ? 'http://localhost:9080' : require('path').join('file://', __dirname, '/index.html')\n  }\n}\n"
  },
  {
    "path": "src/main/configs/protocol.js",
    "content": "/* eslint quote-props: [\"error\", \"always\"] */\nexport default {\n  'task-list': 'application:task-list',\n  'new-task': 'application:new-task',\n  'new-bt-task': 'application:new-bt-task',\n  'pause-all-task': 'application:pause-all-task',\n  'resume-all-task': 'application:resume-all-task',\n  'reveal-in-folder': 'application:reveal-in-folder',\n  'preferences': 'application:preferences',\n  'about': 'application:about'\n}\n"
  },
  {
    "path": "src/main/core/AutoLaunchManager.js",
    "content": "import { app } from 'electron'\n\nimport { LOGIN_SETTING_OPTIONS } from '@shared/constants'\n\nexport default class AutoLaunchManager {\n  enable () {\n    return new Promise((resolve, reject) => {\n      const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin\n      if (enabled) {\n        resolve()\n      }\n\n      app.setLoginItemSettings({\n        ...LOGIN_SETTING_OPTIONS,\n        openAtLogin: true\n      })\n      resolve()\n    })\n  }\n\n  disable () {\n    return new Promise((resolve, reject) => {\n      app.setLoginItemSettings({ openAtLogin: false })\n      resolve()\n    })\n  }\n\n  isEnabled () {\n    return new Promise((resolve, reject) => {\n      const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin\n      resolve(enabled)\n    })\n  }\n}\n"
  },
  {
    "path": "src/main/core/ConfigManager.js",
    "content": "import { app } from 'electron'\nimport is from 'electron-is'\nimport Store from 'electron-store'\n\nimport {\n  getConfigBasePath,\n  getDhtPath,\n  getMaxConnectionPerServer,\n  getUserDownloadsPath\n} from '../utils/index'\nimport {\n  APP_RUN_MODE,\n  APP_THEME,\n  EMPTY_STRING,\n  ENGINE_RPC_PORT,\n  IP_VERSION,\n  LOGIN_SETTING_OPTIONS,\n  NGOSANG_TRACKERS_BEST_IP_URL_CDN,\n  NGOSANG_TRACKERS_BEST_URL_CDN,\n  PROXY_SCOPES,\n  PROXY_SCOPE_OPTIONS\n} from '@shared/constants'\nimport { CHROME_UA } from '@shared/ua'\nimport { separateConfig } from '@shared/utils'\nimport { reduceTrackerString } from '@shared/utils/tracker'\n\nexport default class ConfigManager {\n  constructor () {\n    this.systemConfig = {}\n    this.userConfig = {}\n\n    this.init()\n  }\n\n  init () {\n    this.initUserConfig()\n    this.initSystemConfig()\n  }\n\n  /**\n   * Aria2 Configuration Priority\n   * system.json > built-in aria2.conf\n   * https://aria2.github.io/manual/en/html/aria2c.html\n   *\n   */\n  initSystemConfig () {\n    this.systemConfig = new Store({\n      name: 'system',\n      cwd: getConfigBasePath(),\n      /* eslint-disable quote-props */\n      defaults: {\n        'all-proxy': EMPTY_STRING,\n        'allow-overwrite': false,\n        'auto-file-renaming': true,\n        'bt-exclude-tracker': EMPTY_STRING,\n        'bt-force-encryption': false,\n        'bt-load-saved-metadata': true,\n        'bt-save-metadata': true,\n        'bt-tracker': EMPTY_STRING,\n        'continue': true,\n        'dht-file-path': getDhtPath(IP_VERSION.V4),\n        'dht-file-path6': getDhtPath(IP_VERSION.V6),\n        'dht-listen-port': 26701,\n        'dir': getUserDownloadsPath(),\n        'enable-dht6': true,\n        'follow-metalink': true,\n        'follow-torrent': true,\n        'listen-port': 21301,\n        'max-concurrent-downloads': 5,\n        'max-connection-per-server': getMaxConnectionPerServer(),\n        'max-download-limit': 0,\n        'max-overall-download-limit': 0,\n        'max-overall-upload-limit': 0,\n        'no-proxy': EMPTY_STRING,\n        'pause-metadata': false,\n        'pause': true,\n        'rpc-listen-port': ENGINE_RPC_PORT,\n        'rpc-secret': EMPTY_STRING,\n        'seed-ratio': 2,\n        'seed-time': 2880,\n        'split': getMaxConnectionPerServer(),\n        'user-agent': CHROME_UA\n      }\n      /* eslint-enable quote-props */\n    })\n    this.fixSystemConfig()\n  }\n\n  initUserConfig () {\n    this.userConfig = new Store({\n      name: 'user',\n      cwd: getConfigBasePath(),\n      // Schema need electron-store upgrade to 3.x.x,\n      // but it will cause the application build to fail.\n      // schema: {\n      //   theme: {\n      //     type: 'string',\n      //     enum: ['auto', 'light', 'dark']\n      //   }\n      // },\n      /* eslint-disable quote-props */\n      defaults: {\n        'auto-check-update': is.macOS(),\n        'auto-hide-window': false,\n        'auto-sync-tracker': true,\n        'enable-upnp': true,\n        'engine-max-connection-per-server': getMaxConnectionPerServer(),\n        'favorite-directories': [],\n        'hide-app-menu': is.windows() || is.linux(),\n        'history-directories': [],\n        'keep-seeding': false,\n        'keep-window-state': false,\n        'last-check-update-time': 0,\n        'last-sync-tracker-time': 0,\n        'locale': app.getLocale(),\n        'log-level': 'warn',\n        'new-task-show-downloading': true,\n        'no-confirm-before-delete-task': false,\n        'open-at-login': false,\n        'protocols': { 'magnet': true, 'thunder': false },\n        'proxy': {\n          'enable': false,\n          'server': EMPTY_STRING,\n          'bypass': EMPTY_STRING,\n          'scope': PROXY_SCOPE_OPTIONS\n        },\n        'resume-all-when-app-launched': false,\n        'run-mode': APP_RUN_MODE.STANDARD,\n        'show-progress-bar': true,\n        'task-notification': true,\n        'theme': APP_THEME.AUTO,\n        'tracker-source': [\n          NGOSANG_TRACKERS_BEST_IP_URL_CDN,\n          NGOSANG_TRACKERS_BEST_URL_CDN\n        ],\n        'tray-theme': APP_THEME.AUTO,\n        'tray-speedometer': is.macOS(),\n        'update-channel': 'latest',\n        'window-state': {}\n      }\n      /* eslint-enable quote-props */\n    })\n    this.fixUserConfig()\n  }\n\n  fixSystemConfig () {\n    // Remove aria2c unrecognized options\n    const { others } = separateConfig(this.systemConfig.store)\n    if (others && Object.keys(others).length > 0) {\n      Object.keys(others).forEach(key => {\n        this.systemConfig.delete(key)\n      })\n    }\n\n    const proxy = this.getUserConfig('proxy', { enable: false })\n    const { enable, server, bypass, scope = [] } = proxy\n    if (enable && server && scope.includes(PROXY_SCOPES.DOWNLOAD)) {\n      this.setSystemConfig('all-proxy', server)\n      this.setSystemConfig('no-proxy', bypass)\n    }\n\n    // Fix spawn ENAMETOOLONG on Windows\n    const tracker = reduceTrackerString(this.systemConfig.get('bt-tracker'))\n    this.setSystemConfig('bt-tracker', tracker)\n  }\n\n  fixUserConfig () {\n    // Fix the value of open-at-login when the user delete\n    // the Motrix self-starting item through startup management.\n    const openAtLogin = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin\n    if (this.getUserConfig('open-at-login') !== openAtLogin) {\n      this.setUserConfig('open-at-login', openAtLogin)\n    }\n\n    if (this.getUserConfig('tracker-source').length === 0) {\n      this.setUserConfig('tracker-source', [\n        NGOSANG_TRACKERS_BEST_IP_URL_CDN,\n        NGOSANG_TRACKERS_BEST_URL_CDN\n      ])\n    }\n  }\n\n  getSystemConfig (key, defaultValue) {\n    if (typeof key === 'undefined' &&\n        typeof defaultValue === 'undefined') {\n      return this.systemConfig.store\n    }\n\n    return this.systemConfig.get(key, defaultValue)\n  }\n\n  getUserConfig (key, defaultValue) {\n    if (typeof key === 'undefined' &&\n        typeof defaultValue === 'undefined') {\n      return this.userConfig.store\n    }\n\n    return this.userConfig.get(key, defaultValue)\n  }\n\n  getLocale () {\n    return this.getUserConfig('locale') || app.getLocale()\n  }\n\n  setSystemConfig (...args) {\n    this.systemConfig.set(...args)\n  }\n\n  setUserConfig (...args) {\n    this.userConfig.set(...args)\n  }\n\n  reset () {\n    this.systemConfig.clear()\n    this.userConfig.clear()\n  }\n}\n"
  },
  {
    "path": "src/main/core/Context.js",
    "content": "import logger from './Logger'\nimport {\n  getEnginePath,\n  getAria2BinPath,\n  getAria2ConfPath,\n  getSessionPath\n} from '../utils'\n\nconst { platform, arch } = process\n\nexport default class Context {\n  constructor () {\n    this.init()\n  }\n\n  getLogPath () {\n    const { path } = logger.transports.file.getFile()\n    return path\n  }\n\n  init () {\n    // The key of Context cannot be the same as that of userConfig and systemConfig.\n    this.context = {\n      platform: platform,\n      arch: arch,\n      'log-path': this.getLogPath(),\n      'session-path': getSessionPath(),\n      'engine-path': getEnginePath(platform, arch),\n      'aria2-bin-path': getAria2BinPath(platform, arch),\n      'aria2-conf-path': getAria2ConfPath(platform, arch)\n    }\n\n    logger.info('[Motrix] Context.init===>', this.context)\n  }\n\n  get (key) {\n    if (typeof key === 'undefined') {\n      return this.context\n    }\n\n    return this.context[key]\n  }\n}\n"
  },
  {
    "path": "src/main/core/EnergyManager.js",
    "content": "import { powerSaveBlocker } from 'electron'\n\nimport logger from './Logger'\n\nlet psbId\nexport default class EnergyManager {\n  startPowerSaveBlocker () {\n    logger.info('[Motrix] EnergyManager.startPowerSaveBlocker', psbId)\n    if (psbId && powerSaveBlocker.isStarted(psbId)) {\n      return\n    }\n\n    psbId = powerSaveBlocker.start('prevent-app-suspension')\n    logger.info('[Motrix] start power save blocker:', psbId)\n  }\n\n  stopPowerSaveBlocker () {\n    logger.info('[Motrix] EnergyManager.stopPowerSaveBlocker', psbId)\n    if (typeof psbId === 'undefined' || !powerSaveBlocker.isStarted(psbId)) {\n      return\n    }\n\n    powerSaveBlocker.stop(psbId)\n    logger.info('[Motrix] stop power save blocker:', psbId)\n    psbId = undefined\n  }\n}\n"
  },
  {
    "path": "src/main/core/Engine.js",
    "content": "import { spawn } from 'node:child_process'\nimport { existsSync, writeFile, unlink } from 'node:fs'\nimport is from 'electron-is'\n\nimport logger from './Logger'\nimport { getI18n } from '../ui/Locale'\nimport {\n  getEnginePidPath,\n  getAria2BinPath,\n  getAria2ConfPath,\n  getSessionPath,\n  transformConfig\n} from '../utils/index'\n\nconst { platform, arch } = process\n\nexport default class Engine {\n  // ChildProcess | null\n  static instance = null\n\n  constructor (options = {}) {\n    this.options = options\n\n    this.i18n = getI18n()\n    this.systemConfig = options.systemConfig\n    this.userConfig = options.userConfig\n  }\n\n  start () {\n    const pidPath = getEnginePidPath()\n    logger.info('[Motrix] Engie pid path:', pidPath)\n\n    if (this.instance) {\n      return\n    }\n\n    const binPath = this.getEngineBinPath()\n    const args = this.getStartArgs()\n    this.instance = spawn(binPath, args, {\n      windowsHide: false,\n      stdio: is.dev() ? 'pipe' : 'ignore'\n    })\n    const pid = this.instance.pid.toString()\n    this.writePidFile(pidPath, pid)\n\n    this.instance.once('close', () => {\n      try {\n        unlink(pidPath, (err) => {\n          if (err) {\n            logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)\n          }\n        })\n      } catch (err) {\n        logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)\n      }\n    })\n\n    if (is.dev()) {\n      this.instance.stdout.on('data', (data) => {\n        logger.log('[Motrix] engine stdout===>', data.toString())\n      })\n\n      this.instance.stderr.on('data', (data) => {\n        logger.log('[Motrix] engine stderr===>', data.toString())\n      })\n    }\n  }\n\n  stop () {\n    logger.info('[Motrix] engine.stop.instance')\n    if (this.instance) {\n      this.instance.kill()\n      this.instance = null\n    }\n  }\n\n  writePidFile (pidPath, pid) {\n    writeFile(pidPath, pid, (err) => {\n      if (err) {\n        logger.error(`[Motrix] Write engine process pid failed: ${err}`)\n      }\n    })\n  }\n\n  getEngineBinPath () {\n    const result = getAria2BinPath(platform, arch)\n    const binIsExist = existsSync(result)\n    if (!binIsExist) {\n      logger.error('[Motrix] engine bin is not exist:', result)\n      throw new Error(this.i18n.t('app.engine-missing-message'))\n    }\n\n    return result\n  }\n\n  getStartArgs () {\n    const confPath = getAria2ConfPath(platform, arch)\n\n    const sessionPath = getSessionPath()\n    const sessionIsExist = existsSync(sessionPath)\n\n    let result = [`--conf-path=${confPath}`, `--save-session=${sessionPath}`]\n    if (sessionIsExist) {\n      result = [...result, `--input-file=${sessionPath}`]\n    }\n\n    const extraConfig = {\n      ...this.systemConfig\n    }\n    const keepSeeding = this.userConfig['keep-seeding']\n    const seedRatio = this.systemConfig['seed-ratio']\n    if (keepSeeding || seedRatio === 0) {\n      extraConfig['seed-ratio'] = 0\n      delete extraConfig['seed-time']\n    }\n    console.log('extraConfig===>', extraConfig)\n\n    const extra = transformConfig(extraConfig)\n    result = [...result, ...extra]\n\n    return result\n  }\n\n  isRunning (pid) {\n    try {\n      return process.kill(pid, 0)\n    } catch (e) {\n      return e.code === 'EPERM'\n    }\n  }\n\n  restart () {\n    this.stop()\n    this.start()\n  }\n}\n"
  },
  {
    "path": "src/main/core/EngineClient.js",
    "content": "'use strict'\n\nimport { Aria2 } from '@shared/aria2'\n\nimport logger from './Logger'\nimport {\n  compactUndefined,\n  formatOptionsForEngine\n} from '@shared/utils'\nimport {\n  ENGINE_RPC_HOST,\n  ENGINE_RPC_PORT,\n  EMPTY_STRING\n} from '@shared/constants'\n\nconst defaults = {\n  host: ENGINE_RPC_HOST,\n  port: ENGINE_RPC_PORT,\n  secret: EMPTY_STRING\n}\n\nexport default class EngineClient {\n  static instance = null\n  static client = null\n\n  constructor (options = {}) {\n    this.options = {\n      ...defaults,\n      ...options\n    }\n\n    this.init()\n  }\n\n  init () {\n    this.connect()\n  }\n\n  connect () {\n    logger.info('[Motrix] main engine client connect', this.options)\n    const { host, port, secret } = this.options\n    this.client = new Aria2({\n      host,\n      port,\n      secret\n    })\n  }\n\n  async call (method, ...args) {\n    return this.client.call(method, ...args).catch((err) => {\n      logger.warn('[Motrix] call client fail:', err.message)\n    })\n  }\n\n  async changeGlobalOption (options) {\n    logger.info('[Motrix] change engine global option:', options)\n    const args = formatOptionsForEngine(options)\n\n    return this.call('changeGlobalOption', args)\n  }\n\n  async shutdown (options = {}) {\n    const { force = false } = options\n    const { secret } = this.options\n\n    const method = force ? 'forceShutdown' : 'shutdown'\n    const args = compactUndefined([secret])\n    return this.call(method, ...args)\n  }\n}\n"
  },
  {
    "path": "src/main/core/ExceptionHandler.js",
    "content": "import { app, dialog } from 'electron'\nimport is from 'electron-is'\n\nimport logger from './Logger'\n\nconst defaults = {\n  showDialog: !is.dev()\n}\nexport default class ExceptionHandler {\n  constructor (options) {\n    this.options = {\n      ...defaults,\n      ...options\n    }\n\n    this.setup()\n  }\n\n  setup () {\n    if (is.dev()) {\n      return\n    }\n    const { showDialog } = this.options\n    process.on('uncaughtException', (err) => {\n      const { message, stack } = err\n      logger.error(`[Motrix] Uncaught exception: ${message}`)\n      logger.error(stack)\n\n      if (showDialog && app.isReady()) {\n        dialog.showErrorBox('Error: ', message)\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "src/main/core/Logger.js",
    "content": "import { join } from 'node:path'\nimport is from 'electron-is'\nimport logger from 'electron-log'\n\nimport { IS_PORTABLE, PORTABLE_EXECUTABLE_DIR } from '@shared/constants'\n\nconst level = is.production() ? 'info' : 'silly'\nlogger.transports.file.level = level\n\nif (IS_PORTABLE) {\n  logger.transports.file.resolvePath = () => join(PORTABLE_EXECUTABLE_DIR, 'main.log')\n}\n\nlogger.info('[Motrix] Logger init')\nlogger.warn('[Motrix] Logger init')\n\nexport default logger\n"
  },
  {
    "path": "src/main/core/ProtocolManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { app } from 'electron'\nimport is from 'electron-is'\nimport { parse } from 'querystring'\n\nimport logger from './Logger'\nimport protocolMap from '../configs/protocol'\nimport { ADD_TASK_TYPE } from '@shared/constants'\n\nexport default class ProtocolManager extends EventEmitter {\n  constructor (options = {}) {\n    super()\n    this.options = options\n\n    // package.json:build.protocols[].schemes[]\n    // options.protocols: { 'magnet': true, 'thunder': false }\n    this.protocols = {\n      mo: true,\n      motrix: true,\n      ...options.protocols\n    }\n\n    this.init()\n  }\n\n  init () {\n    const { protocols } = this\n    this.setup(protocols)\n  }\n\n  setup (protocols = {}) {\n    if (is.dev() || is.mas()) {\n      return\n    }\n\n    Object.keys(protocols).forEach((protocol) => {\n      const enabled = protocols[protocol]\n      if (enabled) {\n        if (!app.isDefaultProtocolClient(protocol)) {\n          app.setAsDefaultProtocolClient(protocol)\n        }\n      } else {\n        app.removeAsDefaultProtocolClient(protocol)\n      }\n    })\n  }\n\n  handle (url) {\n    logger.info(`[Motrix] protocol url: ${url}`)\n\n    if (\n      url.toLowerCase().startsWith('ftp:') ||\n      url.toLowerCase().startsWith('http:') ||\n      url.toLowerCase().startsWith('https:') ||\n      url.toLowerCase().startsWith('magnet:') ||\n      url.toLowerCase().startsWith('thunder:')\n    ) {\n      return this.handleResourceProtocol(url)\n    }\n\n    if (\n      url.toLowerCase().startsWith('mo:') ||\n      url.toLowerCase().startsWith('motrix:')\n    ) {\n      return this.handleMoProtocol(url)\n    }\n  }\n\n  handleResourceProtocol (url) {\n    if (!url) {\n      return\n    }\n\n    global.application.sendCommandToAll('application:new-task', {\n      type: ADD_TASK_TYPE.URI,\n      uri: url\n    })\n  }\n\n  handleMoProtocol (url) {\n    const parsed = new URL(url)\n    const { host, search } = parsed\n    logger.info('[Motrix] protocol parsed:', parsed, host)\n\n    const command = protocolMap[host]\n    if (!command) {\n      return\n    }\n\n    const query = search.startsWith('?') ? search.replace('?', '') : search\n    const args = parse(query)\n    global.application.sendCommandToAll(command, args)\n  }\n}\n"
  },
  {
    "path": "src/main/core/UPnPManager.js",
    "content": "import NatAPI from '@motrix/nat-api'\n\nimport logger from './Logger'\n\nlet client = null\nconst mappingStatus = {}\n\nexport default class UPnPManager {\n  constructor (options = {}) {\n    this.options = {\n      ...options\n    }\n  }\n\n  init () {\n    if (client) {\n      return\n    }\n\n    client = new NatAPI({\n      autoUpdate: true\n    })\n  }\n\n  map (port) {\n    this.init()\n\n    return new Promise((resolve, reject) => {\n      logger.info('[Motrix] UPnPManager port mapping: ', port)\n      if (!port) {\n        reject(new Error('[Motrix] port was not specified'))\n        return\n      }\n\n      try {\n        client.map(port, (err) => {\n          if (err) {\n            logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err.message)\n            reject(err.message)\n            return\n          }\n\n          mappingStatus[port] = true\n          logger.info(`[Motrix] UPnPManager port ${port} mapping succeeded`)\n          resolve()\n        })\n      } catch (err) {\n        reject(err.message)\n      }\n    })\n  }\n\n  unmap (port) {\n    this.init()\n\n    return new Promise((resolve, reject) => {\n      logger.info('[Motrix] UPnPManager port unmapping: ', port)\n      if (!port) {\n        reject(new Error('[Motrix] port was not specified'))\n        return\n      }\n\n      if (!mappingStatus[port]) {\n        resolve()\n        return\n      }\n\n      try {\n        client.unmap(port, (err) => {\n          if (err) {\n            logger.warn(`[Motrix] UPnPManager unmap ${port} failed, error: `, err)\n            reject(err.message)\n            return\n          }\n\n          logger.info(`[Motrix] UPnPManager port ${port} unmapping succeeded`)\n          mappingStatus[port] = false\n          resolve()\n        })\n      } catch (err) {\n        reject(err.message)\n      }\n    })\n  }\n\n  closeClient () {\n    if (!client) {\n      return\n    }\n\n    try {\n      client.destroy(() => {\n        client = null\n      })\n    } catch (err) {\n      logger.warn('[Motrix] close UPnP client fail', err)\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/core/UpdateManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { resolve } from 'node:path'\nimport { dialog } from 'electron'\nimport is from 'electron-is'\nimport { autoUpdater } from 'electron-updater'\n\nimport { PROXY_SCOPES } from '@shared/constants'\nimport logger from './Logger'\nimport { getI18n } from '../ui/Locale'\n\nif (is.dev()) {\n  autoUpdater.updateConfigPath = resolve(__dirname, '../../../app-update.yml')\n}\n\nexport default class UpdateManager extends EventEmitter {\n  constructor (options = {}) {\n    super()\n    this.options = options\n    this.i18n = getI18n()\n\n    this.isChecking = false\n    this.updater = autoUpdater\n    this.updater.autoDownload = false\n    this.updater.autoInstallOnAppQuit = false\n    this.updater.logger = logger\n    logger.info('[Motrix] setup proxy:', this.options.proxy)\n    this.setupProxy(this.options.proxy)\n\n    this.autoCheckData = {\n      checkEnable: this.options.autoCheck,\n      userCheck: false\n    }\n    this.init()\n  }\n\n  setupProxy (proxy) {\n    const { enable, server, scope = [] } = proxy\n    if (!enable || !server || !scope.includes(PROXY_SCOPES.UPDATE_APP)) {\n      this.updater.netSession.setProxy({\n        proxyRules: undefined\n      })\n      return\n    }\n\n    const url = new URL(server)\n    const { username, password, protocol = 'http:', host, port } = url\n    const proxyRules = `${protocol}//${host}`\n\n    logger.info(`[Motrix] setup proxy: ${proxyRules}`, username, password, protocol, host, port)\n    this.updater.netSession.setProxy({\n      proxyRules\n    })\n\n    if (server.includes('@')) {\n      this.updater.signals.login((_authInfo, callback) => {\n        callback(username, password)\n      })\n    }\n  }\n\n  init () {\n    // Event: error\n    // Event: checking-for-update\n    // Event: update-available\n    // Event: update-not-available\n    // Event: download-progress\n    // Event: update-downloaded\n\n    this.updater.on('checking-for-update', this.checkingForUpdate.bind(this))\n    this.updater.on('update-available', this.updateAvailable.bind(this))\n    this.updater.on('update-not-available', this.updateNotAvailable.bind(this))\n    this.updater.on('download-progress', this.updateDownloadProgress.bind(this))\n    this.updater.on('update-downloaded', this.updateDownloaded.bind(this))\n    this.updater.on('update-cancelled', this.updateCancelled.bind(this))\n    this.updater.on('error', this.updateError.bind(this))\n\n    if (this.autoCheckData.checkEnable && !this.isChecking) {\n      this.autoCheckData.userCheck = false\n      this.updater.checkForUpdates()\n    }\n  }\n\n  check () {\n    this.autoCheckData.userCheck = true\n    this.updater.checkForUpdates()\n  }\n\n  checkingForUpdate () {\n    this.isChecking = true\n    this.emit('checking')\n  }\n\n  updateAvailable (event, info) {\n    this.emit('update-available', info)\n    dialog.showMessageBox({\n      type: 'info',\n      title: this.i18n.t('app.check-for-updates-title'),\n      message: this.i18n.t('app.update-available-message'),\n      buttons: [this.i18n.t('app.yes'), this.i18n.t('app.no')],\n      cancelId: 1\n    }).then(({ response }) => {\n      if (response === 0) {\n        this.updater.downloadUpdate()\n      } else {\n        this.emit('update-cancelled', info)\n      }\n    })\n  }\n\n  updateNotAvailable (event, info) {\n    this.isChecking = false\n    this.emit('update-not-available', info)\n    if (this.autoCheckData.userCheck) {\n      dialog.showMessageBox({\n        title: this.i18n.t('app.check-for-updates-title'),\n        message: this.i18n.t('app.update-not-available-message')\n      })\n    }\n  }\n\n  /**\n   * autoUpdater:download-progress\n   * @param {Object} event\n   * progress,\n   * bytesPerSecond,\n   * percent,\n   * total,\n   * transferred\n   */\n  updateDownloadProgress (event) {\n    this.emit('download-progress', event)\n  }\n\n  updateDownloaded (event, info) {\n    this.emit('update-downloaded', info)\n    this.updater.logger.log(`Update Downloaded: ${info}`)\n    dialog.showMessageBox({\n      title: this.i18n.t('app.check-for-updates-title'),\n      message: this.i18n.t('app.update-downloaded-message')\n    }).then(_ => {\n      this.isChecking = false\n      this.emit('will-updated')\n      setTimeout(() => {\n        this.updater.quitAndInstall()\n      }, 200)\n    })\n  }\n\n  updateCancelled () {\n    this.isChecking = false\n  }\n\n  updateError (event, error) {\n    this.isChecking = false\n    this.emit('update-error', error)\n    const msg = (error == null)\n      ? this.i18n.t('app.update-error-message')\n      : (error.stack || error).toString()\n\n    this.updater.logger.warn(`[Motrix] update-error: ${msg}`)\n    dialog.showErrorBox('Error', msg)\n  }\n}\n"
  },
  {
    "path": "src/main/index.dev.js",
    "content": "/**\n * This file is used specifically and only for development. It installs\n * `electron-debug` & `vue-devtools`. There shouldn't be any need to\n *  modify this file, but it can be used to extend your development\n *  environment.\n */\n\n/* eslint-disable */\n\n// Install `vue-devtools`\nrequire('electron').app.whenReady().then(() => {\n  let installExtension = require('electron-devtools-installer')\n  installExtension.default(installExtension.VUEJS_DEVTOOLS)\n    .then(() => {})\n    .catch(err => {\n      console.log('Unable to install `vue-devtools`: \\n', err)\n    })\n})\n\n// Require `main` process to boot app\nrequire('./index')\n"
  },
  {
    "path": "src/main/index.js",
    "content": "import { app } from 'electron'\nimport is from 'electron-is'\nimport { initialize } from '@electron/remote/main'\n\nimport Launcher from './Launcher'\n\n/**\n * initialize the main-process side of the remote module\n */\ninitialize()\n\nprocess.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'\n\nif (process.env.NODE_ENV !== 'development') {\n  global.__static = require('path').join(__dirname, '/static').replace(/\\\\/g, '\\\\\\\\')\n}\n\n/**\n * Fix Windows notification func\n * appId defined in .electron-vue/webpack.main.config.js\n */\nif (is.windows()) {\n  app.setAppUserModelId(appId)\n}\n\nglobal.launcher = new Launcher()\n"
  },
  {
    "path": "src/main/menus/darwin.json",
    "content": "{\n  \"menu\": [\n    {\n      \"id\": \"menu.app\",\n      \"submenu\": [\n        { \"id\": \"app.about\", \"command\": \"application:about\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.preferences\", \"command\": \"application:preferences\" },\n        { \"id\": \"app.check-for-updates\", \"command\": \"application:check-for-updates\" },\n        { \"id\": \"app.hide\", \"role\": \"hide\" },\n        { \"id\": \"app.hide-others\", \"role\": \"hideothers\" },\n        { \"id\": \"app.unhide\", \"role\": \"unhide\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.quit\", \"role\": \"quit\" }\n      ]\n    },\n    {\n      \"id\": \"menu.task\",\n      \"submenu\": [\n        { \"id\": \"task.new-task\", \"command\": \"application:new-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.new-bt-task\", \"command\": \"application:new-bt-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.open-file\", \"command\": \"application:open-file\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.task-list\", \"command\": \"application:task-list\" },\n        { \"id\": \"task.pause-task\", \"command\": \"application:pause-task\" },\n        { \"id\": \"task.resume-task\", \"command\": \"application:resume-task\" },\n        { \"id\": \"task.delete-task\", \"command\": \"application:delete-task\" },\n        { \"id\": \"task.move-task-up\", \"command\": \"application:move-task-up\" },\n        { \"id\": \"task.move-task-down\", \"command\": \"application:move-task-down\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"task.pause-all-task\", \"command\": \"application:pause-all-task\" },\n        { \"id\": \"task.resume-all-task\", \"command\": \"application:resume-all-task\" },\n        { \"id\": \"task.select-all-task\", \"command\": \"application:select-all-task\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"task.clear-recent-tasks\", \"command\": \"application:clear-recent-tasks\" }\n      ]\n    },\n    {\n      \"id\": \"menu.edit\",\n      \"submenu\": [\n        { \"id\": \"edit.undo\", \"role\": \"undo\" },\n        { \"id\": \"edit.redo\", \"role\": \"redo\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"edit.cut\", \"role\": \"cut\" },\n        { \"id\": \"edit.copy\", \"role\": \"copy\" },\n        { \"id\": \"edit.paste\", \"role\": \"paste\" },\n        { \"id\": \"edit.delete\", \"role\": \"delete\" },\n        { \"id\": \"edit.select-all\", \"role\": \"selectall\" }\n      ]\n    },\n    {\n      \"role\": \"window\",\n      \"id\": \"menu.window\",\n      \"submenu\": [\n        { \"id\": \"window.reload\", \"role\": \"reload\" },\n        { \"id\": \"window.close\", \"role\": \"close\" },\n        { \"id\": \"window.minimize\", \"role\": \"minimize\" },\n        { \"id\": \"window.zoom\", \"role\": \"zoom\" },\n        { \"id\": \"window.toggle-fullscreen\", \"role\": \"togglefullscreen\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"window.front\", \"role\": \"front\" }\n      ]\n    },\n    {\n      \"role\": \"help\",\n      \"id\": \"menu.help\",\n      \"submenu\": [\n        { \"id\": \"help.official-website\", \"command\": \"help:official-website\" },\n        { \"id\": \"help.manual\", \"command\": \"help:manual\" },\n        { \"id\": \"help.release-notes\", \"command\": \"help:release-notes\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.report-problem\", \"command\": \"help:report-problem\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.toggle-dev-tools\", \"role\": \"toggledevtools\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/main/menus/linux.json",
    "content": "{\n  \"menu\": [\n    {\n      \"id\": \"menu.file\",\n      \"submenu\": [\n        { \"id\": \"app.about\", \"command\": \"application:about\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.preferences\", \"command\": \"application:preferences\" },\n        { \"id\": \"app.check-for-updates\", \"command\": \"application:check-for-updates\" },\n        { \"id\": \"app.show\", \"command\": \"application:show\", \"command-arg\": { \"page\": \"index\" } },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.quit\", \"role\": \"quit\" }\n      ]\n    },\n    {\n      \"id\": \"menu.task\",\n      \"submenu\": [\n        { \"id\": \"task.new-task\", \"command\": \"application:new-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.new-bt-task\", \"command\": \"application:new-bt-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.open-file\", \"command\": \"application:open-file\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.task-list\", \"command\": \"application:task-list\" },\n        { \"id\": \"task.pause-task\", \"command\": \"application:pause-task\" },\n        { \"id\": \"task.resume-task\", \"command\": \"application:resume-task\" },\n        { \"id\": \"task.delete-task\", \"command\": \"application:delete-task\" },\n        { \"id\": \"task.move-task-up\", \"command\": \"application:move-task-up\" },\n        { \"id\": \"task.move-task-down\", \"command\": \"application:move-task-down\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"task.pause-all-task\", \"command\": \"application:pause-all-task\" },\n        { \"id\": \"task.resume-all-task\", \"command\": \"application:resume-all-task\" },\n        { \"id\": \"task.select-all-task\", \"command\": \"application:select-all-task\" }\n      ]\n    },\n    {\n      \"id\": \"menu.edit\",\n      \"submenu\": [\n        { \"id\": \"edit.undo\", \"role\": \"undo\" },\n        { \"id\": \"edit.redo\", \"role\": \"redo\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"edit.cut\", \"role\": \"cut\" },\n        { \"id\": \"edit.copy\", \"role\": \"copy\" },\n        { \"id\": \"edit.paste\", \"role\": \"paste\" },\n        { \"id\": \"edit.delete\", \"role\": \"delete\" },\n        { \"id\": \"edit.select-all\", \"role\": \"selectall\" }\n      ]\n    },\n    {\n      \"role\": \"window\",\n      \"id\": \"menu.window\",\n      \"submenu\": [\n        { \"id\": \"window.reload\", \"role\": \"reload\" },\n        { \"id\": \"window.close\", \"role\": \"close\" },\n        { \"id\": \"window.minimize\", \"role\": \"minimize\" },\n        { \"id\": \"window.zoom\", \"role\": \"zoom\" },\n        { \"id\": \"window.toggle-fullscreen\", \"role\": \"togglefullscreen\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"window.front\", \"role\": \"front\" }\n      ]\n    },\n    {\n      \"role\": \"help\",\n      \"id\": \"menu.help\",\n      \"submenu\": [\n        { \"id\": \"help.official-website\", \"command\": \"help:official-website\" },\n        { \"id\": \"help.manual\", \"command\": \"help:manual\" },\n        { \"id\": \"help.release-notes\", \"command\": \"help:release-notes\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.report-problem\", \"command\": \"help:report-problem\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.toggle-dev-tools\", \"role\": \"toggledevtools\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/main/menus/touchBar.json",
    "content": "[\n  {\n    \"type\": \"button\", \"icon\": \"new-task\", \"id\": \"task.new-task\", \"command\": \"application:new-task\", \"command-after\": \"application:show?page=index\"\n  },\n  {\n    \"type\": \"spacer\", \"size\": \"small\"\n  },\n  {\n    \"type\": \"group\",\n    \"id\": \"task.task-list\",\n    \"items\": [\n      {\n        \"type\": \"button\", \"icon\": \"task-active\", \"command\": \"application:task-list\", \"command-arg\": { \"status\": \"active\" }\n      },\n      {\n        \"type\": \"button\", \"icon\": \"task-waiting\", \"command\": \"application:task-list\", \"command-arg\": { \"status\": \"waiting\" }\n      },\n      {\n        \"type\": \"button\", \"icon\": \"task-stopped\", \"command\": \"application:task-list\", \"command-arg\": { \"status\": \"stopped\" }\n      }\n    ]\n  },\n  {\n    \"type\": \"spacer\", \"size\": \"large\"\n  },\n  {\n    \"type\": \"button\", \"icon\": \"preferences\", \"id\": \"app.preferences\", \"command\": \"application:preferences\"\n  },\n  {\n    \"type\": \"spacer\", \"size\": \"small\"\n  },\n  {\n    \"type\": \"button\", \"icon\": \"about\", \"id\": \"app.about\", \"command\": \"application:about\", \"command-before\": \"application:show?page=index\"\n  }\n]\n"
  },
  {
    "path": "src/main/menus/tray.json",
    "content": "[\n  { \"id\": \"task.new-task\", \"command\": \"application:new-task\", \"command-after\": \"application:show?page=index\" },\n  { \"id\": \"task.new-bt-task\", \"command\": \"application:new-bt-task\", \"command-arg\": { \"type\": \"torrent\" }, \"command-after\": \"application:show?page=index\" },\n  { \"id\": \"task.open-file\", \"command\": \"application:open-file\", \"command-before\": \"application:show?page=index\" },\n  { \"type\": \"separator\" },\n  { \"id\": \"app.show\", \"command\": \"application:show\", \"command-arg\": { \"page\": \"index\" } },\n  { \"id\": \"help.manual\", \"command\": \"help:manual\" },\n  { \"id\": \"app.check-for-updates\", \"command\": \"application:check-for-updates\" },\n  { \"type\": \"separator\" },\n  { \"id\": \"app.task-list\", \"command\": \"application:task-list\", \"command-before\": \"application:show?page=index\" },\n  { \"id\": \"app.preferences\", \"command\": \"application:preferences\", \"command-before\": \"application:show?page=index\" },\n  { \"id\": \"app.quit\", \"command\": \"application:quit\" }\n]\n"
  },
  {
    "path": "src/main/menus/win32.json",
    "content": "{\n  \"menu\": [\n    {\n      \"id\": \"menu.file\",\n      \"submenu\": [\n        { \"id\": \"app.about\", \"command\": \"application:about\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.preferences\", \"command\": \"application:preferences\" },\n        { \"id\": \"app.check-for-updates\", \"command\": \"application:check-for-updates\" },\n        { \"id\": \"app.show\", \"command\": \"application:show\", \"command-arg\": { \"page\": \"index\" } },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.quit\", \"role\": \"quit\" }\n      ]\n    },\n    {\n      \"id\": \"menu.task\",\n      \"submenu\": [\n        { \"id\": \"task.new-task\", \"command\": \"application:new-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.new-bt-task\", \"command\": \"application:new-bt-task\", \"command-after\": \"application:show?page=index\" },\n        { \"id\": \"task.open-file\", \"command\": \"application:open-file\", \"command-before\": \"application:show?page=index\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"app.task-list\", \"command\": \"application:task-list\" },\n        { \"id\": \"task.pause-task\", \"command\": \"application:pause-task\" },\n        { \"id\": \"task.resume-task\", \"command\": \"application:resume-task\" },\n        { \"id\": \"task.delete-task\", \"command\": \"application:delete-task\" },\n        { \"id\": \"task.move-task-up\", \"command\": \"application:move-task-up\" },\n        { \"id\": \"task.move-task-down\", \"command\": \"application:move-task-down\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"task.pause-all-task\", \"command\": \"application:pause-all-task\" },\n        { \"id\": \"task.resume-all-task\", \"command\": \"application:resume-all-task\" },\n        { \"id\": \"task.select-all-task\", \"command\": \"application:select-all-task\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"task.clear-recent-tasks\", \"command\": \"application:clear-recent-tasks\" }\n      ]\n    },\n    {\n      \"id\": \"menu.edit\",\n      \"submenu\": [\n        { \"id\": \"edit.undo\", \"role\": \"undo\" },\n        { \"id\": \"edit.redo\", \"role\": \"redo\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"edit.cut\", \"role\": \"cut\" },\n        { \"id\": \"edit.copy\", \"role\": \"copy\" },\n        { \"id\": \"edit.paste\", \"role\": \"paste\" },\n        { \"id\": \"edit.delete\", \"role\": \"delete\" },\n        { \"id\": \"edit.select-all\", \"role\": \"selectall\" }\n      ]\n    },\n    {\n      \"role\": \"window\",\n      \"id\": \"menu.window\",\n      \"submenu\": [\n        { \"id\": \"window.reload\", \"role\": \"reload\" },\n        { \"id\": \"window.close\", \"role\": \"close\" },\n        { \"id\": \"window.minimize\", \"role\": \"minimize\" },\n        { \"id\": \"window.zoom\", \"role\": \"zoom\" },\n        { \"id\": \"window.toggle-fullscreen\", \"role\": \"togglefullscreen\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"window.front\", \"role\": \"front\" }\n      ]\n    },\n    {\n      \"role\": \"help\",\n      \"id\": \"menu.help\",\n      \"submenu\": [\n        { \"id\": \"help.official-website\", \"command\": \"help:official-website\" },\n        { \"id\": \"help.manual\", \"command\": \"help:manual\" },\n        { \"id\": \"help.release-notes\", \"command\": \"help:release-notes\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.report-problem\", \"command\": \"help:report-problem\" },\n        { \"type\": \"separator\" },\n        { \"id\": \"help.toggle-dev-tools\", \"role\": \"toggledevtools\" }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/main/pages/about.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/main/pages/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/main/ui/DockManager.js",
    "content": "import is from 'electron-is'\nimport { EventEmitter } from 'node:events'\nimport { app } from 'electron'\n\nimport { bytesToSize } from '@shared/utils'\n\nimport {\n  APP_RUN_MODE\n} from '@shared/constants'\n\nconst enabled = is.macOS()\n\nexport default class DockManager extends EventEmitter {\n  constructor (options) {\n    super()\n    this.options = options\n    const { runMode } = this.options\n    if (runMode === APP_RUN_MODE.TRAY) {\n      this.hide()\n    }\n  }\n\n  show = enabled\n    ? () => {\n      if (app.dock.isVisible()) {\n        return\n      }\n\n      return app.dock.show()\n    }\n    : () => {}\n\n  hide = enabled\n    ? () => {\n      if (!app.dock.isVisible()) {\n        return\n      }\n\n      app.dock.hide()\n    }\n    : () => {}\n\n  // macOS setBadge not working\n  // @see https://github.com/electron/electron/issues/25745#issuecomment-702826143\n  setBadge = enabled\n    ? (text) => {\n      app.dock.setBadge(text)\n    }\n    : (text) => {}\n\n  handleSpeedChange = enabled\n    ? (speed) => {\n      const { downloadSpeed } = speed\n      const text = downloadSpeed > 0 ? `${bytesToSize(downloadSpeed)}/s` : ''\n      this.setBadge(text)\n    }\n    : (text) => {}\n\n  openDock = enabled\n    ? (path) => {\n      app.dock.downloadFinished(path)\n    }\n    : (path) => {}\n}\n"
  },
  {
    "path": "src/main/ui/Locale.js",
    "content": "import resources from '@shared/locales/app'\nimport LocaleManager from '@shared/locales/LocaleManager'\n\nconst localeManager = new LocaleManager({\n  resources\n})\n\nexport const getLocaleManager = () => {\n  return localeManager\n}\n\nexport const setupLocaleManager = (locale) => {\n  localeManager.changeLanguageByLocale(locale)\n\n  return localeManager\n}\n\nexport const getI18n = () => {\n  return localeManager.getI18n()\n}\n\nexport const getI18nTranslator = () => {\n  return localeManager.getI18n().t\n}\n"
  },
  {
    "path": "src/main/ui/MenuManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { Menu } from 'electron'\n\nimport keymap from '@shared/keymap'\nimport {\n  translateTemplate,\n  flattenMenuItems,\n  updateStates\n} from '../utils/menu'\nimport { getI18n } from '../ui/Locale'\n\nexport default class MenuManager extends EventEmitter {\n  constructor (options) {\n    super()\n    this.options = options\n    this.i18n = getI18n()\n\n    this.keymap = keymap\n    this.items = {}\n\n    this.load()\n\n    this.setup()\n  }\n\n  load () {\n    const template = require(`../menus/${process.platform}.json`)\n    this.template = template.menu\n  }\n\n  build () {\n    const keystrokesByCommand = {}\n    for (const item in this.keymap) {\n      keystrokesByCommand[this.keymap[item]] = item\n    }\n\n    // Deepclone the menu template to refresh menu\n    const template = JSON.parse(JSON.stringify(this.template))\n    const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)\n    const menu = Menu.buildFromTemplate(tpl)\n    return menu\n  }\n\n  setup () {\n    const menu = this.build()\n    Menu.setApplicationMenu(menu)\n    this.items = flattenMenuItems(menu)\n  }\n\n  handleLocaleChange (locale) {\n    this.setup()\n  }\n\n  updateMenuStates (visibleStates, enabledStates, checkedStates) {\n    updateStates(this.items, visibleStates, enabledStates, checkedStates)\n  }\n\n  updateMenuItemVisibleState (id, flag) {\n    const visibleStates = {\n      [id]: flag\n    }\n    this.updateMenuStates(visibleStates, null, null)\n  }\n\n  updateMenuItemEnabledState (id, flag) {\n    const enabledStates = {\n      [id]: flag\n    }\n    this.updateMenuStates(null, enabledStates, null)\n  }\n}\n"
  },
  {
    "path": "src/main/ui/ThemeManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { nativeTheme } from 'electron'\n\nimport { APP_THEME } from '@shared/constants'\nimport logger from '../core/Logger'\nimport { getSystemTheme } from '../utils'\n\nexport default class ThemeManager extends EventEmitter {\n  constructor (options = {}) {\n    super()\n\n    this.options = options\n    this.init()\n  }\n\n  init () {\n    this.systemTheme = getSystemTheme()\n\n    this.handleEvents()\n  }\n\n  getSystemTheme () {\n    return this.systemTheme\n  }\n\n  handleEvents () {\n    nativeTheme.on('updated', () => {\n      const theme = getSystemTheme()\n      this.systemTheme = theme\n      logger.info('[Motrix] nativeTheme updated===>', theme)\n      this.emit('system-theme-change', theme)\n    })\n  }\n\n  updateSystemTheme (theme) {\n    theme = theme === APP_THEME.AUTO ? 'system' : theme\n    nativeTheme.themeSource = theme\n  }\n}\n"
  },
  {
    "path": "src/main/ui/TouchBarManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { join } from 'node:path'\nimport { TouchBar, nativeImage } from 'electron'\n\nimport { handleCommand } from '../utils/menu'\nimport logger from '../core/Logger'\n\nconst { TouchBarButton, TouchBarLabel, TouchBarSpacer, TouchBarGroup } = TouchBar\n\nexport default class TouchBarManager extends EventEmitter {\n  constructor (options) {\n    super()\n    this.options = options\n    this.bars = {}\n    this.load()\n  }\n\n  load () {\n    this.template = require('../menus/touchBar.json')\n  }\n\n  getClickFn (item) {\n    let fn = () => {}\n    if (item.command) {\n      fn = () => {\n        handleCommand(item)\n      }\n    }\n    return fn\n  }\n\n  getIconImage (icon) {\n    if (!icon) {\n      return\n    }\n    const img = join(__static, `./icons/${icon}.png`)\n    return nativeImage.createFromPath(img)\n  }\n\n  buildItem (type, options) {\n    let result = null\n    const { label, backgroundColor, textColor, size } = options\n\n    switch (type) {\n    case 'button':\n      result = new TouchBarButton({\n        label,\n        backgroundColor,\n        icon: this.getIconImage(options.icon),\n        click: this.getClickFn(options)\n      })\n      break\n    case 'label':\n      result = new TouchBarLabel({\n        label,\n        textColor\n      })\n      break\n    case 'spacer':\n      result = new TouchBarSpacer({ size })\n      break\n    case 'group':\n      result = new TouchBarGroup({\n        items: new TouchBar({\n          items: options.items\n        })\n      })\n      break\n    default:\n      result = null\n    }\n\n    return result\n  }\n\n  build (template) {\n    const result = []\n\n    template.forEach(tpl => {\n      const { id, type, ...rest } = tpl\n      let options = { ...rest }\n      if (type === 'group') {\n        options = { type, items: this.build(options.items) }\n      }\n      const item = this.buildItem(type, options)\n      result.push(item)\n    })\n    return result\n  }\n\n  getTouchBarByPage (page) {\n    let bar = this.bars[page] || null\n    if (!bar) {\n      try {\n        const items = this.build(this.template)\n        bar = new TouchBar({ items })\n        this.bars[page] = bar\n      } catch (e) {\n        logger.info('getTouchBarByPage fail', e)\n      }\n    }\n    return bar\n  }\n\n  setup (page, window) {\n    const bar = this.getTouchBarByPage(page)\n    window.setTouchBar(bar)\n  }\n}\n"
  },
  {
    "path": "src/main/ui/TrayManager.js",
    "content": "import { EventEmitter } from 'node:events'\nimport { join } from 'node:path'\nimport { Tray, Menu, nativeImage } from 'electron'\nimport is from 'electron-is'\n\nimport { APP_RUN_MODE, APP_THEME } from '@shared/constants'\nimport { getInverseTheme } from '@shared/utils'\nimport logger from '../core/Logger'\nimport { getI18n } from './Locale'\nimport {\n  translateTemplate,\n  flattenMenuItems,\n  updateStates\n} from '../utils/menu'\nimport { convertArrayBufferToBuffer } from '../utils/index'\n\nlet tray = null\nconst { platform } = process\n\nexport default class TrayManager extends EventEmitter {\n  constructor (options = {}) {\n    super()\n\n    this.options = options\n    this.theme = options.theme || APP_THEME.AUTO\n\n    this.systemTheme = options.systemTheme\n    this.inverseSystemTheme = getInverseTheme(this.systemTheme)\n    this.macOS = platform === 'darwin'\n\n    this.speedometer = options.speedometer\n    this.runMode = options.runMode\n\n    this.i18n = getI18n()\n    this.menu = null\n    this.cache = {}\n\n    this.uploadSpeed = 0\n    this.downloadSpeed = 0\n    this.status = false\n    this.focused = false\n    this.initialized = false\n\n    this.init()\n  }\n\n  init () {\n    if (tray || this.initialized || this.runMode === APP_RUN_MODE.HIDE_TRAY) {\n      return\n    }\n\n    this.loadTemplate()\n    this.loadImages()\n    this.initTray()\n    this.setupMenu()\n    this.bindEvents()\n\n    this.initialized = true\n  }\n\n  loadTemplate () {\n    this.template = require('../menus/tray.json')\n  }\n\n  loadImages () {\n    switch (platform) {\n    case 'darwin':\n      this.loadImagesForMacOS()\n      break\n    case 'win32':\n      this.loadImagesForWindows()\n      break\n    case 'linux':\n      this.loadImagesForLinux()\n      break\n\n    default:\n      this.loadImagesForDefault()\n      break\n    }\n  }\n\n  loadImagesForMacOS () {\n    this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')\n  }\n\n  loadImagesForWindows () {\n    this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-normal.png')\n    this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-active.png')\n  }\n\n  loadImagesForLinux () {\n    const { theme } = this\n    if (theme === APP_THEME.AUTO) {\n      this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-dark-normal.png')\n      this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-dark-active.png')\n    } else {\n      this.normalIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-normal.png`)\n      this.activeIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-active.png`)\n    }\n  }\n\n  loadImagesForDefault () {\n    this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')\n    this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-light-active.png')\n  }\n\n  getFromCacheOrCreateImage (key) {\n    let file = this.getCache(key)\n    if (file) {\n      return file\n    }\n\n    file = nativeImage.createFromPath(join(__static, `./${key}`))\n    file.setTemplateImage(this.macOS)\n    this.setCache(key, file)\n    return file\n  }\n\n  getCache (key) {\n    return this.cache[key]\n  }\n\n  setCache (key, value) {\n    this.cache[key] = value\n  }\n\n  buildMenu () {\n    const keystrokesByCommand = {}\n    for (const item in this.keymap) {\n      keystrokesByCommand[this.keymap[item]] = item\n    }\n\n    // Deepclone the menu template to refresh menu\n    const template = JSON.parse(JSON.stringify(this.template))\n    const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)\n    this.menu = Menu.buildFromTemplate(tpl)\n    this.items = flattenMenuItems(this.menu)\n  }\n\n  setupMenu () {\n    this.buildMenu()\n\n    this.updateContextMenu()\n  }\n\n  initTray () {\n    const { icon } = this.getIcons()\n    tray = new Tray(icon)\n    // tray.setPressedImage(inverseIcon)\n\n    if (!this.macOS) {\n      tray.setToolTip('Motrix')\n    }\n  }\n\n  bindEvents () {\n    // All OS\n    tray.on('click', this.handleTrayClick)\n\n    // macOS, Windows\n    // tray.on('double-click', this.handleTrayDbClick)\n    tray.on('right-click', this.handleTrayRightClick)\n    tray.on('mouse-down', this.handleTrayMouseDown)\n    tray.on('mouse-up', this.handleTrayMouseUp)\n\n    // macOS only\n    tray.setIgnoreDoubleClickEvents(true)\n    tray.on('drop-files', this.handleTrayDropFiles)\n    tray.on('drop-text', this.handleTrayDropText)\n  }\n\n  unbindEvents () {\n    // All OS\n    tray.removeListener('click', this.handleTrayClick)\n\n    // macOS, Windows\n    tray.removeListener('right-click', this.handleTrayRightClick)\n    tray.removeListener('mouse-down', this.handleTrayMouseDown)\n    tray.removeListener('mouse-up', this.handleTrayMouseUp)\n\n    // macOS only\n    tray.removeListener('drop-files', this.handleTrayDropFiles)\n    tray.removeListener('drop-text', this.handleTrayDropText)\n  }\n\n  handleTrayClick = (event) => {\n    global.application.toggle()\n  }\n\n  handleTrayDbClick = (event) => {\n    global.application.show()\n  }\n\n  handleTrayRightClick = (event) => {\n    tray.popUpContextMenu(this.menu)\n  }\n\n  handleTrayMouseDown = (event) => {\n    this.focused = true\n    this.emit('mouse-down', {\n      focused: true,\n      theme: this.inverseSystemTheme\n    })\n    this.renderTray()\n  }\n\n  handleTrayMouseUp = (event) => {\n    this.focused = false\n    this.emit('mouse-up', {\n      focused: false,\n      theme: this.theme\n    })\n    this.renderTray()\n  }\n\n  handleTrayDropFiles = (event, files) => {\n    this.emit('drop-files', files)\n  }\n\n  handleTrayDropText = (event, text) => {\n    this.emit('drop-text', text)\n  }\n\n  toggleSpeedometer (enabled) {\n    this.speedometer = enabled\n  }\n\n  async renderTray () {\n    if (!tray || this.speedometer) {\n      return\n    }\n\n    const { icon } = this.getIcons()\n\n    tray.setImage(icon)\n    // tray.setPressedImage(inverseIcon)\n\n    this.updateContextMenu()\n  }\n\n  getIcons () {\n    if (this.macOS) {\n      return { icon: this.normalIcon }\n    }\n\n    const { focused, status, systemTheme } = this\n\n    const icon = status ? this.activeIcon : this.normalIcon\n    if (systemTheme === APP_THEME.DARK) {\n      return {\n        icon\n      }\n    }\n\n    const inverseIcon = status ? this.inverseActiveIcon : this.inverseNormalIcon\n\n    return {\n      icon: focused ? inverseIcon : icon\n      // inverseIcon: focused ? icon : inverseIcon\n    }\n  }\n\n  updateContextMenu () {\n    /**\n     * Linux requires setContextMenu to be called\n     * in order for the context menu to populate correctly\n     */\n    if (!tray || process.platform !== 'linux') {\n      return\n    }\n\n    tray.setContextMenu(this.menu)\n  }\n\n  updateMenuStates (visibleStates, enabledStates, checkedStates) {\n    updateStates(this.items, visibleStates, enabledStates, checkedStates)\n\n    this.updateContextMenu()\n  }\n\n  updateMenuItemVisibleState (id, flag) {\n    const visibleStates = {\n      [id]: flag\n    }\n    this.updateMenuStates(visibleStates, null, null)\n  }\n\n  updateMenuItemEnabledState (id, flag) {\n    const enabledStates = {\n      [id]: flag\n    }\n    this.updateMenuStates(null, enabledStates, null)\n  }\n\n  handleLocaleChange (locale) {\n    this.setupMenu()\n  }\n\n  handleRunModeChange (mode) {\n    this.runMode = mode\n\n    if (mode === APP_RUN_MODE.HIDE_TRAY) {\n      this.destroy()\n    } else {\n      this.init()\n    }\n  }\n\n  handleSpeedometerEnableChange (enabled) {\n    this.toggleSpeedometer(enabled)\n\n    this.renderTray()\n  }\n\n  handleSystemThemeChange (systemTheme = APP_THEME.LIGHT) {\n    if (!is.macOS()) {\n      return\n    }\n\n    this.systemTheme = systemTheme\n    this.inverseSystemTheme = getInverseTheme(systemTheme)\n\n    this.loadImages()\n\n    this.renderTray()\n  }\n\n  handleDownloadStatusChange (status) {\n    this.status = status\n\n    this.renderTray()\n  }\n\n  async handleSpeedChange ({ uploadSpeed, downloadSpeed }) {\n    if (!this.speedometer) {\n      return\n    }\n\n    this.uploadSpeed = uploadSpeed\n    this.downloadSpeed = downloadSpeed\n\n    await this.renderTray()\n  }\n\n  async updateTrayByImage (ab) {\n    if (!tray) {\n      return\n    }\n\n    const buffer = convertArrayBufferToBuffer(ab)\n    const image = nativeImage.createFromBuffer(buffer, {\n      scaleFactor: 2\n    })\n    image.setTemplateImage(this.macOS)\n    tray.setImage(image)\n  }\n\n  destroy () {\n    logger.info('[Motrix] TrayManager.destroy')\n    if (tray) {\n      this.unbindEvents()\n    }\n\n    tray.destroy()\n    tray = null\n    this.initialized = false\n  }\n}\n"
  },
  {
    "path": "src/main/ui/WindowManager.js",
    "content": "import { join } from 'node:path'\nimport { EventEmitter } from 'node:events'\nimport { debounce } from 'lodash'\nimport { app, shell, screen, BrowserWindow } from 'electron'\nimport is from 'electron-is'\n\nimport pageConfig from '../configs/page'\nimport logger from '../core/Logger'\n\nconst baseBrowserOptions = {\n  titleBarStyle: 'hiddenInset',\n  show: false,\n  width: 1024,\n  height: 768,\n  backgroundColor: '#fff',\n  webPreferences: {\n    nodeIntegration: true\n  }\n}\n\n// fix: BrowserWindow rendering bug under linux\nconst defaultBrowserOptions = is.macOS()\n  ? {\n    ...baseBrowserOptions,\n    vibrancy: 'ultra-dark',\n    visualEffectState: 'active',\n    backgroundColor: '#00000000'\n  }\n  : {\n    ...baseBrowserOptions\n  }\n\nexport default class WindowManager extends EventEmitter {\n  constructor (options = {}) {\n    super()\n    this.userConfig = options.userConfig || {}\n\n    this.windows = {}\n\n    this.willQuit = false\n\n    this.handleBeforeQuit()\n\n    this.handleAllWindowClosed()\n  }\n\n  setWillQuit (flag) {\n    this.willQuit = flag\n  }\n\n  getPageOptions (page) {\n    const result = pageConfig[page] || {}\n    const hideAppMenu = this.userConfig['hide-app-menu']\n    if (hideAppMenu) {\n      result.attrs.frame = false\n    }\n\n    // Optimized for small screen users\n    const { width, height } = screen.getPrimaryDisplay().workAreaSize\n    const widthScale = width >= 1280 ? 1 : 0.875\n    const heightScale = height >= 800 ? 1 : 0.875\n    result.attrs.width *= widthScale\n    result.attrs.height *= heightScale\n\n    // fix AppImage Dock Icon Missing\n    // https://github.com/AppImage/AppImageKit/wiki/Bundling-Electron-apps\n    if (is.linux()) {\n      result.attrs.icon = join(__static, './512x512.png')\n    }\n\n    return result\n  }\n\n  getPageBounds (page) {\n    const enabled = this.userConfig['keep-window-state']\n    const windowStateMap = this.userConfig['window-state'] || {}\n    let result = null\n    if (enabled) {\n      result = windowStateMap[page]\n    }\n\n    return result\n  }\n\n  openWindow (page, options = {}) {\n    const pageOptions = this.getPageOptions(page)\n    const { hidden } = options\n    const autoHideWindow = this.userConfig['auto-hide-window']\n    let window = this.windows[page] || null\n    if (window) {\n      window.show()\n      window.focus()\n      return window\n    }\n\n    window = new BrowserWindow({\n      ...defaultBrowserOptions,\n      ...pageOptions.attrs,\n      webPreferences: {\n        enableRemoteModule: true,\n        contextIsolation: false,\n        nodeIntegration: true,\n        nodeIntegrationInWorker: true\n      }\n    })\n\n    const bounds = this.getPageBounds(page)\n    if (bounds) {\n      window.setBounds(bounds)\n    }\n\n    if (is.dev() && pageOptions.openDevTools) {\n      window.webContents.openDevTools()\n    }\n\n    window.webContents.setWindowOpenHandler(({ url }) => {\n      shell.openExternal(url)\n      return { action: 'deny' }\n    })\n\n    if (pageOptions.url) {\n      window.loadURL(pageOptions.url)\n    }\n\n    window.once('ready-to-show', () => {\n      if (!hidden) {\n        window.show()\n      }\n    })\n\n    window.on('enter-full-screen', () => {\n      this.emit('enter-full-screen', window)\n    })\n\n    window.on('leave-full-screen', () => {\n      this.emit('leave-full-screen', window)\n    })\n\n    this.handleWindowState(page, window)\n\n    this.handleWindowClose(pageOptions, page, window)\n\n    this.bindAfterClosed(page, window)\n\n    this.addWindow(page, window)\n    if (autoHideWindow) {\n      this.handleWindowBlur()\n    }\n\n    return window\n  }\n\n  getWindow (page) {\n    return this.windows[page]\n  }\n\n  getWindows () {\n    return this.windows || {}\n  }\n\n  getWindowList () {\n    return Object.values(this.getWindows())\n  }\n\n  addWindow (page, window) {\n    this.windows[page] = window\n  }\n\n  destroyWindow (page) {\n    const win = this.getWindow(page)\n    if (!win) {\n      return\n    }\n\n    this.removeWindow(page)\n    win.removeListener('closed')\n    win.removeListener('move')\n    win.removeListener('resize')\n    win.destroy()\n  }\n\n  removeWindow (page) {\n    this.windows[page] = null\n  }\n\n  bindAfterClosed (page, window) {\n    window.on('closed', (event) => {\n      this.removeWindow(page)\n    })\n  }\n\n  handleWindowState (page, window) {\n    window.on('resize', debounce(() => {\n      const bounds = window.getBounds()\n      this.emit('window-resized', { page, bounds })\n    }, 500))\n\n    window.on('move', debounce(() => {\n      const bounds = window.getBounds()\n      this.emit('window-moved', { page, bounds })\n    }, 500))\n  }\n\n  handleWindowClose (pageOptions, page, window) {\n    window.on('close', (event) => {\n      if (pageOptions.bindCloseToHide && !this.willQuit) {\n        event.preventDefault()\n\n        // @see https://github.com/electron/electron/issues/20263\n        if (window.isFullScreen()) {\n          window.once('leave-full-screen', () => window.hide())\n\n          window.setFullScreen(false)\n        } else {\n          window.hide()\n        }\n      }\n      const bounds = window.getBounds()\n      this.emit('window-closed', { page, bounds })\n    })\n  }\n\n  showWindow (page) {\n    const window = this.getWindow(page)\n    if (!window || (window.isVisible() && !window.isMinimized())) {\n      return\n    }\n\n    window.show()\n  }\n\n  hideWindow (page) {\n    const window = this.getWindow(page)\n    if (!window || !window.isVisible()) {\n      return\n    }\n    window.hide()\n  }\n\n  hideAllWindow () {\n    this.getWindowList().forEach((window) => {\n      window.hide()\n    })\n  }\n\n  toggleWindow (page) {\n    const window = this.getWindow(page)\n    if (!window) {\n      return\n    }\n\n    if (!window.isVisible() || window.isFullScreen()) {\n      window.show()\n    } else {\n      window.hide()\n    }\n  }\n\n  getFocusedWindow () {\n    return BrowserWindow.getFocusedWindow()\n  }\n\n  handleBeforeQuit () {\n    app.on('before-quit', () => {\n      this.setWillQuit(true)\n    })\n  }\n\n  onWindowBlur (event, window) {\n    window.hide()\n  }\n\n  handleWindowBlur () {\n    app.on('browser-window-blur', this.onWindowBlur)\n  }\n\n  unbindWindowBlur () {\n    app.removeListener('browser-window-blur', this.onWindowBlur)\n  }\n\n  handleAllWindowClosed () {\n    app.on('window-all-closed', (event) => {\n      event.preventDefault()\n    })\n  }\n\n  sendCommandTo (window, command, ...args) {\n    if (!window) {\n      return\n    }\n    logger.info('[Motrix] send command to:', command, ...args)\n    window.webContents.send('command', command, ...args)\n  }\n\n  sendMessageTo (window, channel, ...args) {\n    if (!window) {\n      return\n    }\n    window.webContents.send(channel, ...args)\n  }\n}\n"
  },
  {
    "path": "src/main/utils/index.js",
    "content": "import { resolve } from 'node:path'\nimport { access, constants, existsSync, lstatSync } from 'node:fs'\nimport { app, nativeTheme, shell } from 'electron'\nimport is from 'electron-is'\n\nimport {\n  APP_THEME,\n  ENGINE_MAX_CONNECTION_PER_SERVER,\n  IP_VERSION,\n  IS_PORTABLE,\n  PORTABLE_EXECUTABLE_DIR\n} from '@shared/constants'\nimport { engineBinMap, engineArchMap } from '../configs/engine'\nimport logger from '../core/Logger'\n\nexport const getUserDataPath = () => {\n  return IS_PORTABLE ? PORTABLE_EXECUTABLE_DIR : app.getPath('userData')\n}\n\nexport const getSystemLogPath = () => {\n  return app.getPath('logs')\n}\n\nexport const getUserDownloadsPath = () => {\n  return app.getPath('downloads')\n}\n\nexport const getConfigBasePath = () => {\n  const path = getUserDataPath()\n  return path\n}\n\nexport const getSessionPath = () => {\n  return resolve(getUserDataPath(), './download.session')\n}\n\nexport const getEnginePidPath = () => {\n  return resolve(getUserDataPath(), './engine.pid')\n}\n\nexport const getDhtPath = (protocol) => {\n  const name = protocol === IP_VERSION.V6 ? 'dht6.dat' : 'dht.dat'\n  return resolve(getUserDataPath(), `./${name}`)\n}\n\nexport const getEngineBin = (platform) => {\n  const result = engineBinMap[platform] || ''\n  return result\n}\n\nexport const getEngineArch = (platform, arch) => {\n  if (!['darwin', 'win32', 'linux'].includes(platform)) {\n    return ''\n  }\n\n  const result = engineArchMap[platform][arch]\n  return result\n}\n\nexport const getDevEnginePath = (platform, arch) => {\n  const ah = getEngineArch(platform, arch)\n  const base = `../../../extra/${platform}/${ah}/engine`\n  const result = resolve(__dirname, base)\n  return result\n}\n\nexport const getProdEnginePath = () => {\n  return resolve(app.getAppPath(), '../engine')\n}\n\nexport const getEnginePath = (platform, arch) => {\n  return is.dev() ? getDevEnginePath(platform, arch) : getProdEnginePath()\n}\n\nexport const getAria2BinPath = (platform, arch) => {\n  const base = getEnginePath(platform, arch)\n  const binName = getEngineBin(platform)\n  const result = resolve(base, `./${binName}`)\n  return result\n}\n\nexport const getAria2ConfPath = (platform, arch) => {\n  const base = getEnginePath(platform, arch)\n  return resolve(base, './aria2.conf')\n}\n\nexport const transformConfig = (config) => {\n  const result = []\n  for (const [k, v] of Object.entries(config)) {\n    if (v !== '') {\n      result.push(`--${k}=${v}`)\n    }\n  }\n  return result\n}\n\nexport const isRunningInDmg = () => {\n  if (!is.macOS() || is.dev()) {\n    return false\n  }\n  const appPath = app.getAppPath()\n  const result = appPath.startsWith('/Volumes/')\n  return result\n}\n\nexport const moveAppToApplicationsFolder = (errorMsg = '') => {\n  return new Promise((resolve, reject) => {\n    try {\n      const result = app.moveToApplicationsFolder()\n      if (result) {\n        resolve(result)\n      } else {\n        reject(new Error(errorMsg))\n      }\n    } catch (err) {\n      reject(err)\n    }\n  })\n}\n\nexport const splitArgv = (argv) => {\n  const args = []\n  const extra = {}\n  for (const arg of argv) {\n    if (arg.startsWith('--')) {\n      const kv = arg.split('=')\n      const key = kv[0]\n      const value = kv[1] || '1'\n      extra[key] = value\n      continue\n    }\n    args.push(arg)\n  }\n  return { args, extra }\n}\n\nexport const parseArgvAsUrl = (argv) => {\n  const arg = argv[1]\n  if (!arg) {\n    return\n  }\n\n  if (checkIsSupportedSchema(arg)) {\n    return arg\n  }\n}\n\nexport const checkIsSupportedSchema = (url = '') => {\n  const str = url.toLowerCase()\n  if (\n    str.startsWith('ftp:') ||\n    str.startsWith('http:') ||\n    str.startsWith('https:') ||\n    str.startsWith('magnet:') ||\n    str.startsWith('thunder:') ||\n    str.startsWith('mo:') ||\n    str.startsWith('motrix:')\n  ) {\n    return true\n  } else {\n    return false\n  }\n}\n\nexport const isDirectory = (path) => {\n  return existsSync(path) && lstatSync(path).isDirectory()\n}\n\nexport const parseArgvAsFile = (argv) => {\n  let arg = argv[1]\n  if (!arg || isDirectory(arg)) {\n    return\n  }\n\n  if (is.linux()) {\n    arg = arg.replace('file://', '')\n  }\n  return arg\n}\n\nexport const getMaxConnectionPerServer = () => {\n  return ENGINE_MAX_CONNECTION_PER_SERVER\n}\n\nexport const getSystemTheme = () => {\n  let result = APP_THEME.LIGHT\n  result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT\n  return result\n}\n\nexport const convertArrayBufferToBuffer = (arrayBuffer) => {\n  const buffer = Buffer.alloc(arrayBuffer.byteLength)\n  const view = new Uint8Array(arrayBuffer)\n  for (let i = 0; i < buffer.length; ++i) {\n    buffer[i] = view[i]\n  }\n  return buffer\n}\n\nexport const showItemInFolder = (fullPath) => {\n  if (!fullPath) {\n    return\n  }\n\n  fullPath = resolve(fullPath)\n  access(fullPath, constants.F_OK, (err) => {\n    if (err) {\n      logger.warn(`[Motrix] ${fullPath} ${err ? 'does not exist' : 'exists'}`)\n      return\n    }\n\n    shell.showItemInFolder(fullPath)\n  })\n}\n"
  },
  {
    "path": "src/main/utils/menu.js",
    "content": "import { parse } from 'querystring'\n\nexport const concat = (template, submenu, submenuToAdd) => {\n  submenuToAdd.forEach(sub => {\n    let relativeItem = null\n    if (sub.position) {\n      switch (sub.position) {\n      case 'first':\n        submenu.unshift(sub)\n        break\n      case 'last':\n        submenu.push(sub)\n        break\n      case 'before':\n        relativeItem = findById(template, sub['relative-id'])\n        if (relativeItem) {\n          const array = relativeItem.__parent\n          const index = array.indexOf(relativeItem)\n          array.splice(index, 0, sub)\n        }\n        break\n      case 'after':\n        relativeItem = findById(template, sub['relative-id'])\n        if (relativeItem) {\n          const array = relativeItem.__parent\n          const index = array.indexOf(relativeItem)\n          array.splice(index + 1, 0, sub)\n        }\n        break\n      default:\n        submenu.push(sub)\n        break\n      }\n    } else {\n      submenu.push(sub)\n    }\n  })\n}\n\nexport const merge = (template, item) => {\n  if (item.id) {\n    const matched = findById(template, item.id)\n    if (matched) {\n      if (item.submenu && Array.isArray(item.submenu)) {\n        if (!Array.isArray(matched.submenu)) {\n          matched.submenu = []\n        }\n        concat(template, matched.submenu, item.submenu)\n      }\n    } else {\n      concat(template, template, [item])\n    }\n  } else {\n    template.push(item)\n  }\n}\n\nfunction findById (template, id) {\n  for (const i in template) {\n    const item = template[i]\n    if (item.id === id) {\n      // Returned item need to have a reference to parent Array (.__parent).\n      // This is required to handle `position` and `relative-id`\n      item.__parent = template\n      return item\n    } else if (Array.isArray(item.submenu)) {\n      const result = findById(item.submenu, id)\n      if (result) {\n        return result\n      }\n    }\n  }\n  return null\n}\n\nexport const translateTemplate = (template, keystrokesByCommand, i18n) => {\n  for (const i in template) {\n    const item = template[i]\n    if (item.command) {\n      item.accelerator = acceleratorForCommand(item.command, keystrokesByCommand)\n    }\n\n    // If label is specified, label is used as the key of i18n.t(key),\n    // which mainly solves the inaccurate translation of item.id.\n    if (i18n) {\n      if (item.label) {\n        item.label = i18n.t(item.label)\n      } else if (item.id) {\n        item.label = i18n.t(item.id)\n      }\n    }\n\n    item.click = () => {\n      handleCommand(item)\n    }\n\n    if (item.submenu) {\n      translateTemplate(item.submenu, keystrokesByCommand, i18n)\n    }\n  }\n  return template\n}\n\nexport const handleCommand = (item) => {\n  handleCommandBefore(item)\n\n  const args = item['command-arg']\n    ? [item.command, item['command-arg']]\n    : [item.command]\n\n  global.application.sendCommandToAll(...args)\n\n  handleCommandAfter(item)\n}\n\nfunction handleCommandBefore (item) {\n  if (!item['command-before']) {\n    return\n  }\n  const [command, params] = item['command-before'].split('?')\n  const args = parse(params)\n  global.application.sendCommandToAll(command, args)\n}\n\nfunction handleCommandAfter (item) {\n  if (!item['command-after']) {\n    return\n  }\n  const [command, params] = item['command-after'].split('?')\n  const args = parse(params)\n  global.application.sendCommandToAll(command, args)\n}\n\nfunction acceleratorForCommand (command, keystrokesByCommand) {\n  const keystroke = keystrokesByCommand[command]\n  if (keystroke) {\n    let modifiers = keystroke.split(/-(?=.)/)\n    const key = modifiers.pop().toUpperCase()\n      .replace('+', 'Plus')\n      .replace('MINUS', '-')\n    modifiers = modifiers.map((modifier) => {\n      if (process.platform === 'darwin') {\n        return modifier.replace(/cmdctrl/ig, 'Cmd')\n          .replace(/shift/ig, 'Shift')\n          .replace(/cmd/ig, 'Cmd')\n          .replace(/ctrl/ig, 'Ctrl')\n          .replace(/alt/ig, 'Alt')\n      } else {\n        return modifier.replace(/cmdctrl/ig, 'Ctrl')\n          .replace(/shift/ig, 'Shift')\n          .replace(/ctrl/ig, 'Ctrl')\n          .replace(/alt/ig, 'Alt')\n      }\n    })\n    const keys = modifiers.concat([key])\n    return keys.join('+')\n  }\n  return null\n}\n\nexport const flattenMenuItems = (menu) => {\n  const flattenItems = {}\n  menu.items.forEach(item => {\n    if (item.id) {\n      flattenItems[item.id] = item\n      if (item.submenu) {\n        Object.assign(flattenItems, flattenMenuItems(item.submenu))\n      }\n    }\n  })\n  return flattenItems\n}\n\nexport const updateStates = (itemsById, visibleStates, enabledStates, checkedStates) => {\n  if (visibleStates) {\n    for (const command in visibleStates) {\n      const item = itemsById[command]\n      if (item) {\n        item.visible = visibleStates[command]\n      }\n    }\n  }\n  if (enabledStates) {\n    for (const command in enabledStates) {\n      const item = itemsById[command]\n      if (item) {\n        item.enabled = enabledStates[command]\n      }\n    }\n  }\n  if (checkedStates) {\n    for (const id in checkedStates) {\n      const item = itemsById[id]\n      if (item) {\n        item.checked = checkedStates[id]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/api/Api.js",
    "content": "import { ipcRenderer } from 'electron'\nimport is from 'electron-is'\nimport { isEmpty, clone } from 'lodash'\nimport { Aria2 } from '@shared/aria2'\nimport {\n  separateConfig,\n  compactUndefined,\n  formatOptionsForEngine,\n  mergeTaskResult,\n  changeKeysToCamelCase,\n  changeKeysToKebabCase\n} from '@shared/utils'\nimport { ENGINE_RPC_HOST } from '@shared/constants'\n\nexport default class Api {\n  constructor (options = {}) {\n    this.options = options\n\n    this.init()\n  }\n\n  async init () {\n    this.config = await this.loadConfig()\n\n    this.client = this.initClient()\n    this.client.open()\n  }\n\n  loadConfigFromLocalStorage () {\n    // TODO\n    const result = {}\n    return result\n  }\n\n  async loadConfigFromNativeStore () {\n    const result = await ipcRenderer.invoke('get-app-config')\n    return result\n  }\n\n  async loadConfig () {\n    let result = is.renderer()\n      ? await this.loadConfigFromNativeStore()\n      : this.loadConfigFromLocalStorage()\n\n    result = changeKeysToCamelCase(result)\n    return result\n  }\n\n  initClient () {\n    const {\n      rpcListenPort: port,\n      rpcSecret: secret\n    } = this.config\n    const host = ENGINE_RPC_HOST\n    return new Aria2({\n      host,\n      port,\n      secret\n    })\n  }\n\n  closeClient () {\n    this.client.close()\n      .then(() => {\n        this.client = null\n      })\n      .catch(err => {\n        console.log('engine client close fail', err)\n      })\n  }\n\n  fetchPreference () {\n    return new Promise((resolve) => {\n      this.config = this.loadConfig()\n      resolve(this.config)\n    })\n  }\n\n  savePreference (params = {}) {\n    const kebabParams = changeKeysToKebabCase(params)\n    if (is.renderer()) {\n      return this.savePreferenceToNativeStore(kebabParams)\n    } else {\n      return this.savePreferenceToLocalStorage(kebabParams)\n    }\n  }\n\n  savePreferenceToLocalStorage () {\n    // TODO\n  }\n\n  savePreferenceToNativeStore (params = {}) {\n    const { user, system, others } = separateConfig(params)\n    const config = {}\n\n    if (!isEmpty(user)) {\n      console.info('[Motrix] save user config: ', user)\n      config.user = user\n    }\n\n    if (!isEmpty(system)) {\n      console.info('[Motrix] save system config: ', system)\n      config.system = system\n      this.updateActiveTaskOption(system)\n    }\n\n    if (!isEmpty(others)) {\n      console.info('[Motrix] save config found illegal key: ', others)\n    }\n\n    ipcRenderer.send('command', 'application:save-preference', config)\n  }\n\n  getVersion () {\n    return this.client.call('getVersion')\n  }\n\n  changeGlobalOption (options) {\n    const args = formatOptionsForEngine(options)\n\n    return this.client.call('changeGlobalOption', args)\n  }\n\n  getGlobalOption () {\n    return new Promise((resolve) => {\n      this.client.call('getGlobalOption')\n        .then((data) => {\n          resolve(changeKeysToCamelCase(data))\n        })\n    })\n  }\n\n  getOption (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n\n    return new Promise((resolve) => {\n      this.client.call('getOption', ...args)\n        .then((data) => {\n          resolve(changeKeysToCamelCase(data))\n        })\n    })\n  }\n\n  updateActiveTaskOption (options) {\n    this.fetchTaskList({ type: 'active' })\n      .then((data) => {\n        if (isEmpty(data)) {\n          return\n        }\n\n        const gids = data.map((task) => task.gid)\n        this.batchChangeOption({ gids, options })\n      })\n  }\n\n  changeOption (params = {}) {\n    const { gid, options = {} } = params\n\n    const engineOptions = formatOptionsForEngine(options)\n    const args = compactUndefined([gid, engineOptions])\n\n    return this.client.call('changeOption', ...args)\n  }\n\n  getGlobalStat () {\n    return this.client.call('getGlobalStat')\n  }\n\n  addUri (params) {\n    const {\n      uris,\n      outs,\n      options\n    } = params\n    const tasks = uris.map((uri, index) => {\n      const engineOptions = formatOptionsForEngine(options)\n      if (outs && outs[index]) {\n        engineOptions.out = outs[index]\n      }\n      const args = compactUndefined([[uri], engineOptions])\n      return ['aria2.addUri', ...args]\n    })\n    return this.client.multicall(tasks)\n  }\n\n  addTorrent (params) {\n    const {\n      torrent,\n      options\n    } = params\n    const engineOptions = formatOptionsForEngine(options)\n    const args = compactUndefined([torrent, [], engineOptions])\n    return this.client.call('addTorrent', ...args)\n  }\n\n  addMetalink (params) {\n    const {\n      metalink,\n      options\n    } = params\n    const engineOptions = formatOptionsForEngine(options)\n    const args = compactUndefined([metalink, engineOptions])\n    return this.client.call('addMetalink', ...args)\n  }\n\n  fetchDownloadingTaskList (params = {}) {\n    const { offset = 0, num = 20, keys } = params\n    const activeArgs = compactUndefined([keys])\n    const waitingArgs = compactUndefined([offset, num, keys])\n    return new Promise((resolve, reject) => {\n      this.client.multicall([\n        ['aria2.tellActive', ...activeArgs],\n        ['aria2.tellWaiting', ...waitingArgs]\n      ]).then((data) => {\n        console.log('[Motrix] fetch downloading task list data:', data)\n        const result = mergeTaskResult(data)\n        resolve(result)\n      }).catch((err) => {\n        console.log('[Motrix] fetch downloading task list fail:', err)\n        reject(err)\n      })\n    })\n  }\n\n  fetchWaitingTaskList (params = {}) {\n    const { offset = 0, num = 20, keys } = params\n    const args = compactUndefined([offset, num, keys])\n    return this.client.call('tellWaiting', ...args)\n  }\n\n  fetchStoppedTaskList (params = {}) {\n    const { offset = 0, num = 20, keys } = params\n    const args = compactUndefined([offset, num, keys])\n    return this.client.call('tellStopped', ...args)\n  }\n\n  fetchActiveTaskList (params = {}) {\n    const { keys } = params\n    const args = compactUndefined([keys])\n    return this.client.call('tellActive', ...args)\n  }\n\n  fetchTaskList (params = {}) {\n    const { type } = params\n    switch (type) {\n    case 'active':\n      return this.fetchDownloadingTaskList(params)\n    case 'waiting':\n      return this.fetchWaitingTaskList(params)\n    case 'stopped':\n      return this.fetchStoppedTaskList(params)\n    default:\n      return this.fetchDownloadingTaskList(params)\n    }\n  }\n\n  fetchTaskItem (params = {}) {\n    const { gid, keys } = params\n    const args = compactUndefined([gid, keys])\n    return this.client.call('tellStatus', ...args)\n  }\n\n  fetchTaskItemWithPeers (params = {}) {\n    const { gid, keys } = params\n    const statusArgs = compactUndefined([gid, keys])\n    const peersArgs = compactUndefined([gid])\n    return new Promise((resolve, reject) => {\n      this.client.multicall([\n        ['aria2.tellStatus', ...statusArgs],\n        ['aria2.getPeers', ...peersArgs]\n      ]).then((data) => {\n        console.log('[Motrix] fetchTaskItemWithPeers:', data)\n        const result = data[0] && data[0][0]\n        const peers = data[1] && data[1][0]\n        result.peers = peers || []\n        console.log('[Motrix] fetchTaskItemWithPeers.result:', result)\n        console.log('[Motrix] fetchTaskItemWithPeers.peers:', peers)\n\n        resolve(result)\n      }).catch((err) => {\n        console.log('[Motrix] fetch downloading task list fail:', err)\n        reject(err)\n      })\n    })\n  }\n\n  fetchTaskItemPeers (params = {}) {\n    const { gid, keys } = params\n    const args = compactUndefined([gid, keys])\n    return this.client.call('getPeers', ...args)\n  }\n\n  pauseTask (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('pause', ...args)\n  }\n\n  pauseAllTask (params = {}) {\n    return this.client.call('pauseAll')\n  }\n\n  forcePauseTask (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('forcePause', ...args)\n  }\n\n  forcePauseAllTask (params = {}) {\n    return this.client.call('forcePauseAll')\n  }\n\n  resumeTask (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('unpause', ...args)\n  }\n\n  resumeAllTask (params = {}) {\n    return this.client.call('unpauseAll')\n  }\n\n  removeTask (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('remove', ...args)\n  }\n\n  forceRemoveTask (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('forceRemove', ...args)\n  }\n\n  saveSession (params = {}) {\n    return this.client.call('saveSession')\n  }\n\n  purgeTaskRecord (params = {}) {\n    return this.client.call('purgeDownloadResult')\n  }\n\n  removeTaskRecord (params = {}) {\n    const { gid } = params\n    const args = compactUndefined([gid])\n    return this.client.call('removeDownloadResult', ...args)\n  }\n\n  multicall (method, params = {}) {\n    let { gids, options = {} } = params\n    options = formatOptionsForEngine(options)\n\n    const data = gids.map((gid, index) => {\n      const _options = clone(options)\n      const args = compactUndefined([gid, _options])\n      return [method, ...args]\n    })\n    return this.client.multicall(data)\n  }\n\n  batchChangeOption (params = {}) {\n    return this.multicall('aria2.changeOption', params)\n  }\n\n  batchRemoveTask (params = {}) {\n    return this.multicall('aria2.remove', params)\n  }\n\n  batchResumeTask (params = {}) {\n    return this.multicall('aria2.unpause', params)\n  }\n\n  batchPauseTask (params = {}) {\n    return this.multicall('aria2.pause', params)\n  }\n\n  batchForcePauseTask (params = {}) {\n    return this.multicall('aria2.forcePause', params)\n  }\n}\n"
  },
  {
    "path": "src/renderer/api/index.js",
    "content": "import Api from './Api'\n\nconst api = new Api()\n\nexport default api\n"
  },
  {
    "path": "src/renderer/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "src/renderer/components/About/AboutPanel.vue",
    "content": "<template>\n  <el-dialog\n    custom-class=\"app-about-dialog\"\n    width=\"61.8vw\"\n    :visible=\"visible\"\n    @open=\"handleOpen\"\n    :before-close=\"handleClose\"\n    @closed=\"handleClosed\">\n    <mo-app-info :version=\"version\" :engine=\"engineInfo\" />\n    <mo-copyright slot=\"footer\" />\n  </el-dialog>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import AppInfo from '@/components/About/AppInfo'\n  import Copyright from '@/components/About/Copyright'\n  import { app } from '@electron/remote'\n\n  export default {\n    name: 'mo-about-panel',\n    components: {\n      [AppInfo.name]: AppInfo,\n      [Copyright.name]: Copyright\n    },\n    props: {\n      visible: {\n        type: Boolean,\n        default: false\n      }\n    },\n    data () {\n      const version = app.getVersion()\n      return {\n        version\n      }\n    },\n    computed: {\n      ...mapState('app', {\n        engineInfo: state => state.engineInfo\n      })\n    },\n    methods: {\n      handleOpen () {\n        this.$store.dispatch('app/fetchEngineInfo')\n      },\n      handleClose (done) {\n        this.$store.dispatch('app/hideAboutPanel')\n      },\n      handleClosed () {\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.app-about-dialog {\n  max-width: 632px;\n  min-width: 380px;\n  .el-dialog__header {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/About/AppInfo.vue",
    "content": "<template>\n  <div class=\"app-info\">\n    <div class=\"app-version\">\n      <mo-logo :width=\"93\" :height=\"21\" style=\"vertical-align: bottom;\" />\n      <span>Version {{version}}</span>\n    </div>\n    <div class=\"app-icon\"></div>\n    <div class=\"engine-info\" v-if=\"!!engine\">\n      <h4>{{ $t('about.engine-version') }} {{engine.version}}</h4>\n      <ul v-if=\"!isMas()\">\n        <li\n          v-for=\"(feature, index) in engine.enabledFeatures\"\n          v-bind:key=\"`feature-${index}`\">\n          {{ feature }}\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import Logo from '@/components/Logo/Logo'\n\n  export default {\n    name: 'mo-app-info',\n    components: {\n      [Logo.name]: Logo\n    },\n    props: {\n      version: {\n        type: String,\n        default: ''\n      },\n      engine: {\n        type: Object,\n        default () {\n          return {\n            version: '',\n            enabledFeatures: []\n          }\n        }\n      }\n    },\n    methods: {\n      isMas: is.mas\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.app-info {\n  position: relative;\n  margin: 8px 0;\n  .app-version span {\n    display: inline-block;\n    vertical-align: bottom;\n    font-size: $--font-size-large;\n    margin-left: 20px;\n    color: $--app-version-color;\n    line-height: 18px;\n  }\n  .app-icon {\n    position: absolute;\n    top: 0;\n    right: 0;\n    background: transparent url('~@/assets/app-icon.png') center center no-repeat;\n    background-size: 128px 128px;\n    width: 128px;\n    height: 128px;\n  }\n  .engine-info {\n    margin: 50px 35% 0 8px;\n    h4 {\n      font-size: $--font-size-base;\n      font-weight: normal;\n      color: $--app-engine-title-color;\n    }\n    ul {\n      font-size: 12px;\n      color: $--app-engine-info-color;\n      list-style: none;\n      padding: 0;\n      line-height: 20px;\n      @include clearfix();\n      li {\n        float: left;\n        width: 50%;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/About/Copyright.vue",
    "content": "<template>\n  <el-row class=\"copyright\">\n    <el-col :span=\"6\" class=\"copyright-left\">\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://motrix.app/\">\n        &copy;{{ year }} Motrix\n      </a>\n    </el-col>\n    <el-col :span=\"18\" class=\"copyright-right\">\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://motrix.app/license\">\n        {{ $t('about.license') }}\n      </a>\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://motrix.app/about\">\n        {{ $t('about.about') }}\n      </a>\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://motrix.app/support\">\n        {{ $t('about.support') }}\n      </a>\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://motrix.app/release\">\n        {{ $t('about.release') }}\n      </a>\n    </el-col>\n  </el-row>\n</template>\n\n<script>\n  export default {\n    name: 'mo-copyright',\n    data () {\n      const year = new Date().getFullYear()\n      return {\n        year\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.copyright {\n  width: 100%;\n  font-size: $--font-size-small;\n  a {\n    color: $--app-copyright-color;\n    text-decoration: none;\n  }\n}\n.copyright-left {\n  text-align: left;\n}\n\n.copyright-right {\n  text-align: right;\n  a {\n    margin-left: 30px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Aside/Index.vue",
    "content": "<template>\n  <el-aside width=\"78px\" :class=\"['aside', 'hidden-sm-and-down', { 'draggable': asideDraggable }]\" :style=\"vibrancy\">\n    <div class=\"aside-inner\">\n      <mo-logo-mini />\n      <ul class=\"menu top-menu\">\n        <li @click=\"nav('/task')\" class=\"non-draggable\">\n          <mo-icon name=\"menu-task\" width=\"20\" height=\"20\" />\n        </li>\n        <li @click=\"showAddTask()\" class=\"non-draggable\">\n          <mo-icon name=\"menu-add\" width=\"20\" height=\"20\" />\n        </li>\n      </ul>\n      <ul class=\"menu bottom-menu\">\n        <li @click=\"nav('/preference')\" class=\"non-draggable\">\n          <mo-icon name=\"menu-preference\" width=\"20\" height=\"20\" />\n        </li>\n        <li @click=\"showAboutPanel\" class=\"non-draggable\">\n          <mo-icon name=\"menu-about\" width=\"20\" height=\"20\" />\n        </li>\n      </ul>\n    </div>\n  </el-aside>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapState } from 'vuex'\n  import { ADD_TASK_TYPE } from '@shared/constants'\n  import LogoMini from '@/components/Logo/LogoMini'\n  import '@/components/Icons/menu-task'\n  import '@/components/Icons/menu-add'\n  import '@/components/Icons/menu-preference'\n  import '@/components/Icons/menu-about'\n\n  export default {\n    name: 'mo-aside',\n    components: {\n      [LogoMini.name]: LogoMini\n    },\n    computed: {\n      ...mapState('app', {\n        currentPage: state => state.currentPage\n      }),\n      asideDraggable () {\n        return is.macOS()\n      },\n      vibrancy () {\n        return is.macOS()\n          ? {\n            backgroundColor: 'transparent'\n          }\n          : {}\n      }\n    },\n    methods: {\n      showAddTask (taskType = ADD_TASK_TYPE.URI) {\n        this.$store.dispatch('app/showAddTaskDialog', taskType)\n      },\n      showAboutPanel () {\n        this.$store.dispatch('app/showAboutPanel')\n      },\n      nav (page) {\n        this.$router.push({\n          path: page\n        }).catch(err => {\n          console.log(err)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.aside-inner {\n  display: flex;\n  height: 100%;\n  flex-flow: column;\n}\n.logo-mini {\n  margin-top: 40px;\n}\n.menu {\n  list-style: none;\n  padding: 0;\n  margin: 0 auto;\n  user-select: none;\n  cursor: default;\n  > li {\n    width: 32px;\n    height: 32px;\n    margin-top: 24px;\n    cursor: pointer;\n    border-radius: 16px;\n    transition: background-color 0.25s;\n    &:hover {\n      background-color: rgba(255, 255, 255, 0.15);\n    }\n  }\n  svg {\n    padding: 6px;\n    color: #fff;\n  }\n}\n.top-menu {\n  flex: 1;\n}\n.bottom-menu {\n  margin-bottom: 24px;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Browser/index.vue",
    "content": "<template>\n  <div ref=\"webviewViewport\" class=\"webview-viewport\">\n    <iframe\n      class=\"mo-webview\"\n      ref=\"iframe\"\n      :src=\"src\"\n    ></iframe>\n  </div>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { webContents } from '@electron/remote'\n  import { Loading } from 'element-ui'\n\n  export default {\n    name: 'mo-browser',\n    components: {\n    },\n    props: {\n      src: {\n        type: String,\n        default: ''\n      }\n    },\n    data () {\n      return {\n        loading: null\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer()\n    },\n    mounted () {\n      const { iframe } = this.$refs\n\n      iframe.addEventListener('did-start-loading', this.loadStart.bind(this))\n      iframe.addEventListener('did-stop-loading', this.loadStop.bind(this))\n      iframe.addEventListener('dom-ready', this.ready.bind(this))\n    },\n    methods: {\n      loadStart () {\n        const { webviewViewport } = this.$refs\n        this.loading = Loading.service({\n          target: webviewViewport\n        })\n      },\n      loadStop () {\n        this.$nextTick(() => {\n          this.loading.close()\n        })\n      },\n      ready () {\n        const { iframe } = this.$refs\n\n        const wc = webContents.fromId(iframe.getWebContentsId())\n        wc.setWindowOpenHandler((event, url) => {\n          event.preventDefault()\n          this.$electron.ipcRenderer.send('command', 'application:open-external', url)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.webview-viewport {\n  position: relative;\n}\n.mo-webview {\n  display: inline-flex;;\n  flex: 1;\n  flex-basis: auto;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/CommandManager/index.js",
    "content": "import { EventEmitter } from 'node:events'\n\nexport default class CommandManager extends EventEmitter {\n  constructor () {\n    super()\n\n    this.commands = {}\n  }\n\n  register (id, fn) {\n    if (this.commands[id]) {\n      console.log('[Motrix] Attempting to register an already-registered command: ' + id)\n      return null\n    }\n    if (!id || !fn) {\n      console.error('[Motrix] Attempting to register a command with a missing id, or command function.')\n      return null\n    }\n    this.commands[id] = fn\n\n    this.emit('commandRegistered', id)\n  }\n\n  unregister (id) {\n    if (this.commands[id]) {\n      delete this.commands[id]\n\n      this.emit('commandUnregistered', id)\n    }\n  }\n\n  execute (id, ...args) {\n    const fn = this.commands[id]\n    if (fn) {\n      try {\n        this.emit('beforeExecuteCommand', id)\n      } catch (err) {\n        console.error(err)\n      }\n      const result = fn(...args)\n      return result\n    } else {\n      return false\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/components/CommandManager/instance.js",
    "content": "import CommandManager from '.'\n\nexport const commands = new CommandManager()\n"
  },
  {
    "path": "src/renderer/components/DragSelect/Index.vue",
    "content": "<template>\n  <div\n    ref=\"container\"\n    style=\"position: relative; user-select: none; overflow-x: hidden; touch-action: none;\"\n  >\n    <slot v-bind=\"{ selected: intersected }\" />\n  </div>\n</template>\n\n<script>\n  const getCoords = (e, containerRect) => ({\n    x: e.clientX - containerRect.left,\n    y: e.clientY - containerRect.top\n  })\n\n  const getDimensions = (p1, p2) => ({\n    width: Math.abs(p1.x - p2.x),\n    height: Math.abs(p1.y - p2.y)\n  })\n\n  const collisionCheck = (node1, node2) =>\n    node1.left < node2.left + node2.width &&\n    node1.left + node1.width > node2.left &&\n    node1.top < node2.top + node2.height &&\n    node1.top + node1.height > node2.top\n\n  export default {\n    name: 'mo-drag-select',\n    props: {\n      attribute: {\n        type: String,\n        required: true\n      },\n      color: {\n        type: String,\n        default: '#bad7fb'\n      },\n      opacity: {\n        type: Number,\n        default: 0.7\n      }\n    },\n    data () {\n      return {\n        intersected: [],\n        children: []\n      }\n    },\n    watch: {\n      intersected (i) {\n        this.$emit('change', i)\n      }\n    },\n    mounted () {\n      const { container } = this.$refs\n      const self = this\n\n      let containerRect = container.getBoundingClientRect()\n      const box = this.createBox()\n      let start = { x: 0, y: 0 }\n      let end = { x: 0, y: 0 }\n\n      function touchStart (e) {\n        e.preventDefault()\n        startDrag(e.touches[0])\n      }\n\n      function touchMove (e) {\n        e.preventDefault()\n        drag(e.touches[0])\n      }\n\n      function startDrag (e) {\n        containerRect = container.getBoundingClientRect()\n        self.children = container.childNodes\n        start = getCoords(e, containerRect)\n        end = start\n        document.addEventListener('mousemove', drag)\n        document.addEventListener('touchmove', touchMove)\n\n        box.style.top = start.y + 'px'\n        box.style.left = start.x + 'px'\n\n        container.prepend(box)\n        self.intersection(box)\n      }\n\n      function drag (e) {\n        end = getCoords(e, containerRect)\n        const dimensions = getDimensions(start, end)\n\n        if (end.x < start.x) {\n          box.style.left = end.x + 'px'\n        }\n        if (end.y < start.y) {\n          box.style.top = end.y + 'px'\n        }\n        box.style.width = dimensions.width + 'px'\n        box.style.height = dimensions.height + 'px'\n\n        self.intersection(box)\n      }\n\n      function endDrag () {\n        start = { x: 0, y: 0 }\n        end = { x: 0, y: 0 }\n\n        box.style.width = 0\n        box.style.height = 0\n\n        document.removeEventListener('mousemove', drag)\n        document.removeEventListener('touchmove', touchMove)\n        box.remove()\n      }\n\n      container.addEventListener('mousedown', startDrag)\n      container.addEventListener('touchstart', touchStart)\n\n      document.addEventListener('mouseup', endDrag)\n      document.addEventListener('touchend', endDrag)\n\n      this.$once('on:destroy', () => {\n        container.removeEventListener('mousedown', startDrag)\n        container.removeEventListener('touchstart', touchStart)\n        document.removeEventListener('mouseup', endDrag)\n        document.removeEventListener('touchend', endDrag)\n      })\n    },\n    methods: {\n      createBox () {\n        const box = document.createElement('div')\n        box.setAttribute('data-drag-box-component', '')\n        box.style.position = 'absolute'\n        box.style.backgroundColor = this.color\n        box.style.opacity = this.opacity\n        box.style.zIndex = 1000\n\n        return box\n      },\n      intersection (box) {\n        const { children } = this\n        const rect = box.getBoundingClientRect()\n        const intersected = []\n\n        for (let i = 0; i < children.length; i++) {\n          if (collisionCheck(rect, children[i].getBoundingClientRect())) {\n            const attr = children[i].getAttribute(this.attribute)\n            if (children[i].hasAttribute(this.attribute)) {\n              intersected.push(attr)\n            }\n          }\n        }\n\n        if (\n          JSON.stringify([...intersected]) !==\n          JSON.stringify([...this.intersected])\n        ) { this.intersected = intersected }\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Dragger/Index.vue",
    "content": "<template>\r\n  <div v-if=\"false\"></div>\r\n</template>\r\n\r\n<script>\r\n  import { ADD_TASK_TYPE } from '@shared/constants'\r\n\r\n  export default {\r\n    name: 'mo-dragger',\r\n    mounted () {\r\n      this.preventDefault = ev => ev.preventDefault()\r\n      let count = 0\r\n      this.onDragEnter = (ev) => {\r\n        if (count === 0) {\r\n          this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)\r\n        }\r\n        count++\r\n      }\r\n\r\n      this.onDragLeave = (ev) => {\r\n        count--\r\n        if (count === 0) {\r\n          this.$store.dispatch('app/hideAddTaskDialog')\r\n        }\r\n      }\r\n\r\n      this.onDrop = (ev) => {\r\n        count = 0\r\n\r\n        const fileList = [...ev.dataTransfer.files]\r\n          .map(item => ({ raw: item, name: item.name }))\r\n          .filter(item => /\\.torrent$/.test(item.name))\r\n        if (!fileList.length) {\r\n          this.$msg.error(this.$t('task.select-torrent'))\r\n        }\r\n      }\r\n\r\n      document.addEventListener('dragover', this.preventDefault)\r\n      document.body.addEventListener('dragenter', this.onDragEnter)\r\n      document.body.addEventListener('dragleave', this.onDragLeave)\r\n      document.body.addEventListener('drop', this.onDrop)\r\n    },\r\n    destroyed () {\r\n      document.removeEventListener('dragover', this.preventDefault)\r\n      document.body.removeEventListener('dragenter', this.onDragEnter)\r\n      document.body.removeEventListener('dragleave', this.onDragLeave)\r\n      document.body.removeEventListener('drop', this.onDrop)\r\n    }\r\n  }\r\n</script>\r\n"
  },
  {
    "path": "src/renderer/components/Icons/Icon.vue",
    "content": "<template>\n  <svg\n    version=\"1.1\"\n    :class=\"klass\"\n    :role=\"label ? 'img' : 'presentation'\"\n    :aria-label=\"label\"\n    :x=\"x\"\n    :y=\"y\"\n    :width=\"width\"\n    :height=\"height\"\n    :viewBox=\"box\"\n    :style=\"style\"\n  >\n    <slot>\n      <template v-if=\"icon && icon.paths\">\n        <path v-for=\"(path, i) in icon.paths\" :key=\"`path-${i}`\" v-bind=\"path\" />\n      </template>\n      <template v-if=\"icon && icon.polygons\">\n        <polygon v-for=\"(polygon, i) in icon.polygons\" :key=\"`polygon-${i}`\" v-bind=\"polygon\" />\n      </template>\n      <template v-if=\"icon && icon.raw\"><g v-bind=\"icon.g\" v-html=\"raw\" /></template>\n    </slot>\n  </svg>\n</template>\n\n<script>\n  const icons = {}\n\n  export default {\n    name: 'mo-icon',\n    props: {\n      name: {\n        type: String,\n        validator (val) {\n          if (val && !(val in icons)) {\n            console.warn(`Invalid prop: prop \"name\" is referring to an unregistered icon \"${val}\".` +\n              '\\nPlease make sure you have imported this icon before using it.')\n            return false\n          }\n          return true\n        }\n      },\n      scale: [Number, String],\n      spin: Boolean,\n      inverse: Boolean,\n      pulse: Boolean,\n      flip: {\n        validator (val) {\n          return val === 'horizontal' || val === 'vertical'\n        }\n      },\n      label: String\n    },\n    data () {\n      return {\n        x: false,\n        y: false,\n        childrenWidth: 0,\n        childrenHeight: 0,\n        outerScale: 1\n      }\n    },\n    computed: {\n      normalizedScale () {\n        let scale = this.scale\n        scale = typeof scale === 'undefined' ? 1 : Number(scale)\n        if (isNaN(scale) || scale <= 0) {\n          console.warn('Invalid prop: prop \"scale\" should be a number over 0.', this)\n          return this.outerScale\n        }\n        return scale * this.outerScale\n      },\n      klass () {\n        return {\n          'mo-icon': true,\n          'mo-spin': this.spin,\n          'mo-flip-horizontal': this.flip === 'horizontal',\n          'mo-flip-vertical': this.flip === 'vertical',\n          'mo-inverse': this.inverse,\n          'mo-pulse': this.pulse,\n          [this.$options.name]: true\n        }\n      },\n      icon () {\n        if (this.name) {\n          return icons[this.name]\n        }\n        return null\n      },\n      box () {\n        if (this.icon) {\n          return `0 0 ${this.icon.width} ${this.icon.height}`\n        }\n        return `0 0 ${this.width} ${this.height}`\n      },\n      ratio () {\n        if (!this.icon) {\n          return 1\n        }\n        const { width, height } = this.icon\n        return Math.max(width, height) / 16\n      },\n      width () {\n        return this.childrenWidth || (this.icon && this.icon.width / this.ratio * this.normalizedScale) || 0\n      },\n      height () {\n        return this.childrenHeight || (this.icon && this.icon.height / this.ratio * this.normalizedScale) || 0\n      },\n      style () {\n        if (this.normalizedScale === 1) {\n          return false\n        }\n        return {\n          fontSize: this.normalizedScale + 'em'\n        }\n      },\n      raw () {\n        // generate unique id for each icon's SVG element with ID\n        if (!this.icon || !this.icon.raw) {\n          return null\n        }\n        let raw = this.icon.raw\n        const ids = {}\n        raw = raw.replace(/\\s(?:xml:)?id=([\"']?)([^\"')\\s]+)\\1/g, (match, quote, id) => {\n          const uniqueId = getId()\n          ids[id] = uniqueId\n          return ` id=\"${uniqueId}\"`\n        })\n        raw = raw.replace(/#(?:([^'\")\\s]+)|xpointer\\(id\\((['\"]?)([^')]+)\\2\\)\\))/g, (match, rawId, _, pointerId) => {\n          const id = rawId || pointerId\n          if (!id || !ids[id]) {\n            return match\n          }\n\n          return `#${ids[id]}`\n        })\n\n        return raw\n      }\n    },\n    mounted () {\n      if (!this.name && this.$children.length === 0) {\n        console.warn('Invalid prop: prop \"name\" is required.')\n        return\n      }\n\n      if (this.icon) {\n        return\n      }\n\n      let width = 0\n      let height = 0\n      this.$children.forEach((child) => {\n        child.outerScale = this.normalizedScale\n\n        width = Math.max(width, child.width)\n        height = Math.max(height, child.height)\n      })\n      this.childrenWidth = width\n      this.childrenHeight = height\n      this.$children.forEach((child) => {\n        child.x = (width - child.width) / 2\n        child.y = (height - child.height) / 2\n      })\n    },\n    register (data) {\n      for (const name in data) {\n        const icon = data[name]\n\n        if (!icon.paths) {\n          icon.paths = []\n        }\n        if (icon.d) {\n          icon.paths.push({ d: icon.d })\n        }\n\n        if (!icon.polygons) {\n          icon.polygons = []\n        }\n        if (icon.points) {\n          icon.polygons.push({ points: icon.points })\n        }\n\n        icons[name] = icon\n      }\n    },\n    icons\n  }\n\n  let cursor = 0xD4937\n  function getId () {\n    return `mo-${(cursor++).toString(16)}`\n  }\n</script>\n\n<style>\n.mo-icon {\n  display: inline-block;\n  fill: currentColor;\n}\n\n.mo-flip-horizontal {\n  transform: scale(-1, 1);\n}\n\n.mo-flip-vertical {\n  transform: scale(1, -1);\n}\n\n.mo-spin {\n  animation: mo-spin 0.5s 0s infinite linear;\n}\n\n.mo-inverse {\n  color: #fff;\n}\n\n.mo-pulse {\n  animation: mo-spin 1s infinite steps(8);\n}\n\n@keyframes mo-spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Icons/arrow-down.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'arrow-down': {\n    'width': 24,\n    'height': 24,\n    'raw': `<g class=\"nc-icon-wrapper\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" stroke=\"currentColor\">\n      <line data-cap=\"butt\" data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"2\" x2=\"12\" y2=\"22\"/>\n      <polyline fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" points=\"19,15 12,22 5,15 \"/>\n    </g>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/arrow-up.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'arrow-up': {\n    'width': 24,\n    'height': 24,\n    'raw': `<g class=\"nc-icon-wrapper\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" stroke=\"currentColor\">\n      <line data-cap=\"butt\" data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"22\" x2=\"12\" y2=\"2\"/>\n      <polyline fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" points=\"5,9 12,2 19,9 \"/>\n    </g>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/audio.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'audio': {\n    'width': 24,\n    'height': 24,\n    'raw': `<rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\"/>\n      <line data-color=\"color-2\" x1=\"1\" y1=\"6\" x2=\"1\" y2=\"18\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-color=\"color-2\" x1=\"23\" y1=\"6\" x2=\"23\" y2=\"18\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <polyline data-cap=\"butt\" points=\"10 15 10 8 16 8 16 14\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\"/>\n      <circle data-stroke=\"none\" cx=\"9\" cy=\"15\" r=\"2\" stroke=\"none\"/>\n      <circle data-stroke=\"none\" cx=\"15\" cy=\"14\" r=\"2\" stroke=\"none\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/delete.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'delete': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line fill=\"none\" stroke-miterlimit=\"10\" x1=\"19\" y1=\"5\" x2=\"5\" y2=\"19\" /><line fill=\"none\" stroke-miterlimit=\"10\" x1=\"19\" y1=\"19\" x2=\"5\" y2=\"5\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/dice.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'dice': {\n    'width': 24,\n    'height': 24,\n    'raw': `<polyline data-cap=\"butt\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" points=\"17,23 17,7 1,7 \"/>\n      <line data-cap=\"butt\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" x1=\"17\" y1=\"7\" x2=\"23\" y2=\"1\"/>\n      <polygon fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" points=\"17,23 23,17 23,1 7,1 1,7 1,23 \"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"12\" cy=\"12\" r=\"1\" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"6\" cy=\"12\" r=\"1\" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"12\" cy=\"18\" r=\"1\" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"6\" cy=\"18\" r=\"1\" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/document.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'document': {\n    'width': 24,\n    'height': 24,\n    'raw': `<rect x=\"3\" y=\"1\" width=\"18\" height=\"22\"></rect>\n      <line x1=\"15\" y1=\"6\" x2=\"17\" y2=\"6\"></line>\n      <line x1=\"15\" y1=\"10\" x2=\"17\" y2=\"10\"></line>\n      <line x1=\"7\" y1=\"14\" x2=\"17\" y2=\"14\"></line>\n      <line x1=\"7\" y1=\"18\" x2=\"17\" y2=\"18\"></line>\n      <rect x=\"7\" y=\"6\" width=\"4\" height=\"4\"></rect>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2',\n      'fill': 'none'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/folder.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'folder': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"8\" x2=\"23\" y2=\"8\" /><polygon fill=\"none\" stroke-miterlimit=\"10\" points=\"23,23 1,23 1,1 10,1 12,4 23,4 \" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/image.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'image': {\n    'width': 24,\n    'height': 24,\n    'raw': `<polyline data-cap=\"butt\" data-color=\"color-2\" points=\"1 20 6 14 10 18 17 10 23 17\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <rect x=\"1\" y=\"3\" width=\"22\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\"/>\n      <circle data-color=\"color-2\" cx=\"9\" cy=\"8\" r=\"2\" fill=\"none\" stroke-miterlimit=\"10\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/inbox.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'inbox': {\n    'width': 24,\n    'height': 24,\n    'raw': `<polyline data-cap=\"butt\" fill=\"none\" stroke-miterlimit=\"10\" points=\"23,15 16,15 16,18 8,18 8,15 1,15 \"/>\n      <line data-cap=\"butt\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"1\" x2=\"12\" y2=\"11\"/>\n      <polyline fill=\"none\" stroke-miterlimit=\"10\" points=\"19,6 20,6 23,15 23,23 1,23 1,15 4,6 5,6 \"/>\n      <polyline fill=\"none\" stroke-miterlimit=\"10\" points=\" 15,8 12,11 9,8 \"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '1.5'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/info-circle.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'info-circle': {\n    'width': 24,\n    'height': 24,\n    'raw': `<circle cx=\"12\" cy=\"12\" r=\"11\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-color=\"color-2\" x1=\"11.959\" y1=\"11\" x2=\"11.959\" y2=\"17\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"11.959\" cy=\"7\" r=\"1\" stroke=\"none\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/info-square.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'info-square': {\n    'width': 24,\n    'height': 24,\n    'raw': `<rect x=\"2\" y=\"2\" width=\"20\" height=\"20\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-color=\"color-2\" x1=\"12\" y1=\"11\" x2=\"12\" y2=\"17\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"12\" cy=\"7\" r=\"1\" stroke=\"none\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/link.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'link': {\n    'width': 24,\n    'height': 24,\n    'raw': `<path fill=\"none\" stroke-miterlimit=\"10\" d=\"M11,6l2.5-2.5 c1.9-1.9,5.1-1.9,7,0l0,0c1.9,1.9,1.9,5.1,0,7L18,13\" /><path fill=\"none\" stroke-miterlimit=\"10\" d=\"M13,18l-2.5,2.5 c-1.9,1.9-5.1,1.9-7,0l0,0c-1.9-1.9-1.9-5.1,0-7L6,11\"/><line fill=\"none\" stroke-miterlimit=\"10\" x1=\"9\" y1=\"15\" x2=\"15\" y2=\"9\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/magnet.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'magnet': {\n    'width': 24,\n    'height': 24,\n    'raw': `\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"8.2\" y1=\"4.5\" x2=\"11.1\" y2=\"7.3\"></line>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"16.7\" y1=\"12.9\" x2=\"19.5\" y2=\"15.8\"></line>\n      <path fill=\"none\" stroke-miterlimit=\"10\"\n        d=\"M12.5,17.2 c-1.6,1.6-4.1,1.6-5.7,0c-1.6-1.6-1.6-4.1,0-5.7l7.1-7.1l-2.8-2.8L4,8.7C0.9,11.8,0.9,16.9,4,20s8.2,3.1,11.3,0l7.1-7.1l-2.8-2.8 L12.5,17.2z\">\n      </path>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/menu-about.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'menu-about': {\n    'width': 24,\n    'height': 24,\n    'raw': `<g stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" fill=\"#fff\" stroke=\"#fff\"><circle fill=\"none\" stroke=\"#fff\" stroke-miterlimit=\"10\" cx=\"12\" cy=\"12\" r=\"11\"/> <path data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" d=\"M12,15v-2 c1.609,0,3-1.391,3-3s-1.391-3-3-3c-1.194,0-2.342,0.893-2.792,1.921\"/> <circle data-color=\"color-2\" data-stroke=\"none\" cx=\"12\" cy=\"18\" r=\"1\" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"/></g>`\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/menu-add.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'menu-add': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"2\" x2=\"12\" y2=\"22\" />\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"22\" y1=\"12\" x2=\"2\" y2=\"12\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/menu-preference.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'menu-preference': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line fill=\"none\" stroke-miterlimit=\"10\" x1=\"14\" y1=\"4\" x2=\"23\" y2=\"4\"/> <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"4\" x2=\"4\" y2=\"4\"/> <line data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"22\" y1=\"12\" x2=\"23\" y2=\"12\"/> <line data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"12\" x2=\"12\" y2=\"12\"/> <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"14\" y1=\"20\" x2=\"23\" y2=\"20\"/> <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"20\" x2=\"4\" y2=\"20\"/> <circle fill=\"none\" stroke-miterlimit=\"10\" cx=\"7\" cy=\"4\" r=\"3\"/> <circle data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" cx=\"15\" cy=\"12\" r=\"3\"/> <circle fill=\"none\" stroke-miterlimit=\"10\" cx=\"7\" cy=\"20\" r=\"3\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/menu-task.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'menu-task': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M14,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h13c0.6,0,1,0.4,1,1S14.6,13,14,13z'\n    }, {\n      'd': 'M23,6H1C0.4,6,0,5.6,0,5s0.4-1,1-1h22c0.6,0,1,0.4,1,1S23.6,6,23,6z'\n    }, {\n      'd': 'M23,20H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h22c0.6,0,1,0.4,1,1S23.6,20,23,20z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/more.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'more': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M3,15 C2.17187086,15 1.46484668,14.7070342 0.87890625,14.1210938 C0.29296582,13.5351533 0,12.8281291 0,12 C0,11.1718709 0.29296582,10.4648467 0.87890625,9.87890625 C1.46484668,9.29296582 2.17187086,9 3,9 C3.82812914,9 4.53515332,9.29296582 5.12109375,9.87890625 C5.70703418,10.4648467 6,11.1718709 6,12 C6,12.8281291 5.70703418,13.5351533 5.12109375,14.1210938 C4.53515332,14.7070342 3.82812914,15 3,15 Z M3,10.5 C2.59374797,10.5 2.24218898,10.648436 1.9453125,10.9453125 C1.64843602,11.242189 1.5,11.593748 1.5,12 C1.5,12.406252 1.64843602,12.757811 1.9453125,13.0546875 C2.24218898,13.351564 2.59374797,13.5 3,13.5 C3.40625203,13.5 3.75781102,13.351564 4.0546875,13.0546875 C4.35156398,12.757811 4.5,12.406252 4.5,12 C4.5,11.593748 4.35156398,11.242189 4.0546875,10.9453125 C3.75781102,10.648436 3.40625203,10.5 3,10.5 Z M12,15 C11.1718709,15 10.4648467,14.7070342 9.87890625,14.1210938 C9.29296582,13.5351533 9,12.8281291 9,12 C9,11.1718709 9.29296582,10.4648467 9.87890625,9.87890625 C10.4648467,9.29296582 11.1718709,9 12,9 C12.8281291,9 13.5351533,9.29296582 14.1210938,9.87890625 C14.7070342,10.4648467 15,11.1718709 15,12 C15,12.8281291 14.7070342,13.5351533 14.1210938,14.1210938 C13.5351533,14.7070342 12.8281291,15 12,15 Z M12,10.5 C11.593748,10.5 11.242189,10.648436 10.9453125,10.9453125 C10.648436,11.242189 10.5,11.593748 10.5,12 C10.5,12.406252 10.648436,12.757811 10.9453125,13.0546875 C11.242189,13.351564 11.593748,13.5 12,13.5 C12.406252,13.5 12.757811,13.351564 13.0546875,13.0546875 C13.351564,12.757811 13.5,12.406252 13.5,12 C13.5,11.593748 13.351564,11.242189 13.0546875,10.9453125 C12.757811,10.648436 12.406252,10.5 12,10.5 Z M21,9 C21.8281291,9 22.5351533,9.29296582 23.1210938,9.87890625 C23.7070342,10.4648467 24,11.1718709 24,12 C24,12.8281291 23.7070342,13.5351533 23.1210938,14.1210938 C22.5351533,14.7070342 21.8281291,15 21,15 C20.1718709,15 19.4648467,14.7070342 18.8789062,14.1210938 C18.2929658,13.5351533 18,12.8281291 18,12 C18,11.1718709 18.2929658,10.4648467 18.8789062,9.87890625 C19.4648467,9.29296582 20.1718709,9 21,9 Z M21,13.5 C21.406252,13.5 21.757811,13.351564 22.0546875,13.0546875 C22.351564,12.757811 22.5,12.406252 22.5,12 C22.5,11.593748 22.351564,11.242189 22.0546875,10.9453125 C21.757811,10.648436 21.406252,10.5 21,10.5 C20.593748,10.5 20.242189,10.648436 19.9453125,10.9453125 C19.648436,11.242189 19.5,11.593748 19.5,12 C19.5,12.406252 19.648436,12.757811 19.9453125,13.0546875 C20.242189,13.351564 20.593748,13.5 21,13.5 Z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/node.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'node': {\n    'width': 24,\n    'height': 24,\n    'raw': `\n      <polyline data-cap=\"butt\" fill=\"none\" stroke-miterlimit=\"10\" points=\"12,9 12,13 7.8,16.1 \"/>\n      <line data-cap=\"butt\" fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"13\" x2=\"16.2\" y2=\"16.1\"/>\n      <circle fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" cx=\"12\" cy=\"5\" r=\"4\"/>\n      <circle fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" cx=\"5\" cy=\"19\" r=\"4\"/>\n      <circle fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" cx=\"19\" cy=\"19\" r=\"4\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/preference-advanced.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'preference-advanced': {\n    'width': 24,\n    'height': 24,\n    'polygons': [{\n      'points': '10.853,9.439 6.707,5.293 8,4 4,0 0,4 4,8 5.293,6.707 9.189,10.603'\n    }],\n    'paths': [{\n      'd': 'M18.94,13.94C18.631,13.976,18.318,14,18,14c-0.305,0-0.608-0.018-0.912-0.053l-3.641,4.499 l4.518,4.518c1.381,1.381,3.619,1.381,5,0v0c1.381-1.381,1.381-3.619,0-5L18.94,13.94z'\n    }, {\n      'd': 'M20.271,6.771l-3.042-3.042l3.208-3.208C19.692,0.189,18.869,0,18,0c-3.314,0-6,2.686-6,6 c0,0.594,0.089,1.166,0.25,1.708l-10.789,8.73c-0.891,0.787-1.423,1.919-1.459,3.106c-0.037,1.188,0.424,2.351,1.264,3.19 C2.082,23.551,3.167,24,4.321,24c1.239,0,2.421-0.532,3.241-1.461l8.73-10.789C16.834,11.911,17.406,12,18,12c3.314,0,6-2.686,6-6 c0-0.869-0.189-1.692-0.521-2.438L20.271,6.771z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/preference-basic.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'preference-basic': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M6.5,11h11c3,0,5.5-2.5,5.5-5.5S20.5,0,17.5,0h-11C3.5,0,1,2.5,1,5.5S3.5,11,6.5,11z M6.5,2 C8.4,2,10,3.6,10,5.5S8.4,9,6.5,9S3,7.4,3,5.5S4.6,2,6.5,2z'\n    }, {\n      'd': 'M17.5,13h-11c-3,0-5.5,2.5-5.5,5.5S3.5,24,6.5,24h11c3,0,5.5-2.5,5.5-5.5S20.5,13,17.5,13z M17.5,22c-1.9,0-3.5-1.6-3.5-3.5s1.6-3.5,3.5-3.5s3.5,1.6,3.5,3.5S19.4,22,17.5,22z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/preference-lab.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'preference-lab': {\n    'width': 24,\n    'height': 24,\n    'polygons': [{\n      'points': '9,11.675 5.855,16 18.145,16 15,11.675 15,5 17,5 17,0 7,0 7,5 9,5'\n    }],\n    'paths': [{\n      'd': 'M19.6,18H4.4l-0.898,1.235c-0.668,0.917-0.763,2.115-0.248,3.126S4.793,24,5.928,24h12.145 c1.135,0,2.159-0.628,2.674-1.639s0.42-2.209-0.248-3.126L19.6,18z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/purge.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'purge': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"9\" x2=\"23\" y2=\"9\"/>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"3\" x2=\"23\" y2=\"3\"/>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"15\" x2=\"11\" y2=\"15\"/>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"21\" x2=\"11\" y2=\"21\"/>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"16\" y1=\"15\" x2=\"22\" y2=\"21\"/>\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"22\" y1=\"15\" x2=\"16\" y2=\"21\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/refresh.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'refresh': {\n    'width': 24,\n    'height': 24,\n    'raw': `<path data-cap=\"butt\" fill=\"none\" stroke-miterlimit=\"10\" d=\"M22,12c0,5.5-4.5,10-10,10 S2,17.5,2,12S6.5,2,12,2c3.9,0,7.3,2.2,8.9,5.5\"/>\n      <polyline fill=\"none\" stroke-miterlimit=\"10\" points=\"21.8,1.7 21,7.6 15,6.8 \"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/speedometer.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'speedometer': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M12,3.609375 C13.1562558,3.609375 14.2460886,3.82812281 15.2695312,4.265625 C16.2929739,4.70312719 17.18359,5.30077746 17.9414062,6.05859375 C18.6992225,6.81641004 19.2968728,7.70702613 19.734375,8.73046875 C20.1718772,9.75391137 20.390625,10.8437442 20.390625,12 C20.390625,12.2187511 20.3242194,12.3984368 20.1914062,12.5390625 C20.0585931,12.6796882 19.8750012,12.75 19.640625,12.75 C19.4218739,12.75 19.2421882,12.6796882 19.1015625,12.5390625 C18.9609368,12.3984368 18.890625,12.2187511 18.890625,12 C18.890625,11.0468702 18.7109393,10.1484417 18.3515625,9.3046875 C17.9921857,8.46093328 17.5039093,7.73047184 16.8867188,7.11328125 C16.2695282,6.49609066 15.5390667,6.0078143 14.6953125,5.6484375 C13.8515583,5.2890607 12.9531298,5.109375 12,5.109375 C11.5468727,5.109375 11.0859398,5.14843711 10.6171875,5.2265625 C10.1484352,5.30468789 9.71093953,5.43749906 9.3046875,5.625 C9.11718656,5.70312539 8.92578223,5.7070316 8.73046875,5.63671875 C8.53515527,5.5664059 8.39843789,5.43750094 8.3203125,5.25 C8.24218711,5.06249906 8.2382809,4.87109473 8.30859375,4.67578125 C8.3789066,4.48046777 8.50781156,4.34375039 8.6953125,4.265625 C9.22656516,4.04687391 9.76952848,3.88281305 10.3242187,3.7734375 C10.878909,3.66406195 11.4374972,3.609375 12,3.609375 Z M5.25,8.3203125 C5.43750094,8.39843789 5.57421832,8.53515527 5.66015625,8.73046875 C5.74609418,8.92578223 5.73437555,9.11718656 5.625,9.3046875 C5.43749906,9.71093953 5.30468789,10.1406227 5.2265625,10.59375 C5.14843711,11.0468773 5.109375,11.5156226 5.109375,12 C5.109375,12.2187511 5.0390632,12.3984368 4.8984375,12.5390625 C4.7578118,12.6796882 4.57812609,12.75 4.359375,12.75 C4.12499883,12.75 3.94140691,12.6796882 3.80859375,12.5390625 C3.67578059,12.3984368 3.609375,12.2187511 3.609375,12 C3.609375,11.4374972 3.66406195,10.878909 3.7734375,10.3242188 C3.88281305,9.76952848 4.04687391,9.22656516 4.265625,8.6953125 C4.34375039,8.50781156 4.48046777,8.3789066 4.67578125,8.30859375 C4.87109473,8.2382809 5.06249906,8.24218711 5.25,8.3203125 Z M12,0 C13.6562583,0 15.2109302,0.316403086 16.6640625,0.94921875 C18.1171948,1.58203441 19.3867133,2.44140082 20.4726562,3.52734375 C21.5585992,4.61328668 22.4179656,5.88280523 23.0507812,7.3359375 C23.6835969,8.78906977 24,10.3437417 24,12 C24,13.6562583 23.6835969,15.2109302 23.0507812,16.6640625 C22.4179656,18.1171948 21.5585992,19.3867133 20.4726562,20.4726562 C19.3867133,21.5585992 18.1171948,22.4179656 16.6640625,23.0507812 C15.2109302,23.6835969 13.6562583,24 12,24 C10.3437417,24 8.78906977,23.6835969 7.3359375,23.0507812 C5.88280523,22.4179656 4.61328668,21.5585992 3.52734375,20.4726562 C2.44140082,19.3867133 1.58203441,18.1171948 0.94921875,16.6640625 C0.316403086,15.2109302 0,13.6562583 0,12 C0,10.3437417 0.316403086,8.78906977 0.94921875,7.3359375 C1.58203441,5.88280523 2.44140082,4.61328668 3.52734375,3.52734375 C4.61328668,2.44140082 5.88280523,1.58203441 7.3359375,0.94921875 C8.78906977,0.316403086 10.3437417,0 12,0 Z M12,22.5 C13.4375072,22.5 14.7968686,22.222659 16.078125,21.6679688 C17.3593814,21.1132785 18.4726515,20.3593798 19.4179688,19.40625 C20.363286,18.4531202 21.1132785,17.3398501 21.6679688,16.0664062 C22.222659,14.7929624 22.5,13.4375072 22.5,12 C22.5,10.5624928 22.222659,9.20313141 21.6679688,7.921875 C21.1132785,6.64061859 20.3593798,5.52734848 19.40625,4.58203125 C18.4531202,3.63671402 17.3398501,2.88672152 16.0664062,2.33203125 C14.7929624,1.77734098 13.4375072,1.5 12,1.5 C10.5624928,1.5 9.20313141,1.77734098 7.921875,2.33203125 C6.64061859,2.88672152 5.52734848,3.64062023 4.58203125,4.59375 C3.63671402,5.54687977 2.88672152,6.66014988 2.33203125,7.93359375 C1.77734098,9.20703762 1.5,10.5624928 1.5,12 C1.5,13.4375072 1.77734098,14.7929624 2.33203125,16.0664062 C2.88672152,17.3398501 3.64062023,18.4531202 4.59375,19.40625 C5.54687977,20.3593798 6.66014988,21.1132785 7.93359375,21.6679688 C9.20703762,22.222659 10.5624928,22.5 12,22.5 Z M12,9 C12.8281291,9 13.5351533,9.29296582 14.1210938,9.87890625 C14.7070342,10.4648467 15,11.1718709 15,12 C15,12.8281291 14.7070342,13.5351533 14.1210938,14.1210938 C13.5351533,14.7070342 12.8281291,15 12,15 C11.1718709,15 10.4648467,14.7070342 9.87890625,14.1210938 C9.29296582,13.5351533 9,12.8281291 9,12 C9,11.7343737 9.03906211,11.4726575 9.1171875,11.2148438 C9.19531289,10.95703 9.3046868,10.7187511 9.4453125,10.5 L5.765625,6.8203125 C5.6249993,6.6796868 5.5546875,6.50781352 5.5546875,6.3046875 C5.5546875,6.10156148 5.6249993,5.92187578 5.765625,5.765625 C5.92187578,5.6249993 6.10156148,5.5546875 6.3046875,5.5546875 C6.50781352,5.5546875 6.6796868,5.6249993 6.8203125,5.765625 L10.5,9.4453125 C10.7187511,9.3046868 10.95703,9.19531289 11.2148438,9.1171875 C11.4726575,9.03906211 11.7343737,9 12,9 Z M12,13.5 C12.406252,13.5 12.757811,13.351564 13.0546875,13.0546875 C13.351564,12.757811 13.5,12.406252 13.5,12 C13.5,11.593748 13.351564,11.242189 13.0546875,10.9453125 C12.757811,10.648436 12.406252,10.5 12,10.5 C11.593748,10.5 11.242189,10.648436 10.9453125,10.9453125 C10.648436,11.242189 10.5,11.593748 10.5,12 C10.5,12.406252 10.648436,12.757811 10.9453125,13.0546875 C11.242189,13.351564 11.593748,13.5 12,13.5 Z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/sync.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'sync': {\n    'width': 24,\n    'height': 24,\n    'raw': `<g stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\n      <path fill=\"none\" stroke-miterlimit=\"10\" d=\"M3,14V4 c0-0.552,0.448-1,1-1h16c0.552,0,1,0.448,1,1v6\"></path>\n      <path fill=\"none\" stroke-miterlimit=\"10\" d=\"M10,18H1v0 c0,1.657,1.343,3,3,3h6\"></path>\n      <path data-cap=\"butt\" data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" d=\"M14.126,17 c0.444-1.725,2.01-3,3.874-3c1.48,0,2.772,0.804,3.464,1.999\"></path>\n      <polygon data-color=\"color-2\" data-stroke=\"none\" points=\"23.22,13.649 22.792,18 18.522,17.061 \" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"></polygon>\n      <path data-cap=\"butt\" data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" d=\"M21.874,20 c-0.444,1.725-2.01,3-3.874,3c-1.48,0-2.772-0.804-3.464-1.999\"></path>\n      <polygon data-color=\"color-2\" data-stroke=\"none\" points=\"12.78,23.351 13.208,19 17.478,19.939 \" stroke-linejoin=\"miter\" stroke-linecap=\"square\" stroke=\"none\"></polygon>\n    </g>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-history.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-history': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M19,13h-8V5h2v6h6V13z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-pause-line.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-pause-line': {\n    'width': 24,\n    'height': 24,\n    'raw': `<rect x=\"3\" y=\"2\" fill=\"none\" stroke-miterlimit=\"10\" width=\"6\" height=\"20\" /><rect x=\"15\" y=\"2\" fill=\"none\" stroke-miterlimit=\"10\" width=\"6\" height=\"20\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-pause.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-pause': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M9,1H3C2.447,1,2,1.447,2,2v20c0,0.553,0.447,1,1,1h6c0.553,0,1-0.447,1-1V2C10,1.447,9.553,1,9,1z'\n    }, {\n      'd': 'M21,1h-6c-0.553,0-1,0.447-1,1v20c0,0.553,0.447,1,1,1h6c0.553,0,1-0.447,1-1V2C22,1.447,21.553,1,21,1z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-restart.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-restart': {\n    'width': 24,\n    'height': 24,\n    'raw': `<path data-cap=\"butt\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" d=\"M6.121,20.121 C7.727,21.22,9.907,22,12,22c5.523,0,10-4.477,10-10c0-5.523-4.477-10-10-10C8.101,2,4.728,4.233,3.078,7.488\"/>\n      <polyline fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\" points=\"2.278,1.588 3.078,7.488 9.078,6.688 \"/>\n      <circle data-color=\"color-2\" fill=\"none\" stroke-miterlimit=\"10\" cx=\"4\" cy=\"18\" r=\"3\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-start-line.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-start-line': {\n    'width': 24,\n    'height': 24,\n    'raw': `<polygon fill=\"none\" stroke-miterlimit=\"10\" points=\"5,22 5,2 20,12 \"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-start.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-start': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M20.555,11.168l-15-10c-0.307-0.204-0.702-0.224-1.026-0.05C4.203,1.292,4,1.631,4,2v20 c0,0.369,0.203,0.708,0.528,0.882C4.676,22.961,4.838,23,5,23c0.194,0,0.388-0.057,0.555-0.168l15-10C20.833,12.646,21,12.334,21,12 S20.833,11.354,20.555,11.168z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-stop-line.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-stop-line': {\n    'width': 24,\n    'height': 24,\n    'raw': `<rect x=\"2\" y=\"2\" fill=\"none\" stroke-miterlimit=\"10\" width=\"20\" height=\"20\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/task-stop.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'task-stop': {\n    'width': 24,\n    'height': 24,\n    'paths': [{\n      'd': 'M22,1H2C1.447,1,1,1.447,1,2v20c0,0.553,0.447,1,1,1h20c0.553,0,1-0.447,1-1V2C23,1.447,22.553,1,22,1z'\n    }]\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/trash.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'trash': {\n    'width': 24,\n    'height': 24,\n    'raw': `<polyline fill=\"none\" stroke-miterlimit=\"10\" points=\"20,9 20,23 4,23 4,9 \" />\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"1\" y1=\"5\" x2=\"23\" y2=\"5\" />\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"12\" y1=\"12\" x2=\"12\" y2=\"18\" />\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"8\" y1=\"12\" x2=\"8\" y2=\"18\" />\n      <line fill=\"none\" stroke-miterlimit=\"10\" x1=\"16\" y1=\"12\" x2=\"16\" y2=\"18\" />\n      <polyline fill=\"none\" stroke-miterlimit=\"10\" points=\"8,5 8,1 16,1 16,5 \" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/video.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'video': {\n    'width': 24,\n    'height': 24,\n    'raw': `<line data-cap=\"butt\" data-color=\"color-2\" x1=\"2\" y1=\"6\" x2=\"22\" y2=\"6\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"12\" y1=\"2\" x2=\"12\" y2=\"22\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"7\" y1=\"2\" x2=\"7\" y2=\"6\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"17\" y1=\"2\" x2=\"17\" y2=\"6\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"2\" y1=\"18\" x2=\"22\" y2=\"18\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"7\" y1=\"22\" x2=\"7\" y2=\"18\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <line data-cap=\"butt\" data-color=\"color-2\" x1=\"17\" y1=\"22\" x2=\"17\" y2=\"18\" fill=\"none\" stroke-miterlimit=\"10\"/>\n      <rect x=\"2\" y=\"2\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-miterlimit=\"10\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '2'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/win-close.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'win-close': {\n    'width': 12,\n    'height': 12,\n    'raw': `<line x1=\"1.5\" y1=\"1.5\" x2=\"10.5\" y2=\"10.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      <line x1=\"10.5\" y1=\"1.5\" x2=\"1.5\" y2=\"10.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '1'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/win-maximize.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'win-maximize': {\n    'width': 12,\n    'height': 12,\n    'raw': `<polyline points=\"5.5 1.5 10.5 1.5 10.5 6.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      <polyline points=\"1.5 5.5 1.5 10.5 6.5 10.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '1'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Icons/win-minimize.js",
    "content": "import Icon from '@/components/Icons/Icon'\n\nIcon.register({\n  'win-minimize': {\n    'width': 12,\n    'height': 12,\n    'raw': `<line x1=\"1\" y1=\"6\" x2=\"11\" y2=\"6\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>`,\n    'g': {\n      'stroke': 'currentColor',\n      'stroke-linecap': 'round',\n      'stroke-linejoin': 'round',\n      'stroke-width': '1'\n    }\n  }\n})\n"
  },
  {
    "path": "src/renderer/components/Locale/index.js",
    "content": "import resources from '@shared/locales/all'\nimport LocaleManager from '@shared/locales/LocaleManager'\n\nconst localeManager = new LocaleManager({\n  resources\n})\n\nexport function getLocaleManager () {\n  return localeManager\n}\n"
  },
  {
    "path": "src/renderer/components/Logo/Logo.vue",
    "content": "<template>\n  <div class=\"logo\">\n    <a target=\"_blank\" href=\"https://motrix.app/\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" :width=\"width\" :height=\"height\" viewBox=\"0 0 62 14\">\n        <g fill-rule=\"evenodd\">\n          <path d=\"M40,2 C40,1 41,1.53477231e-14 42,1.53477231e-14 C42,1.53477231e-14 60,1.27897692e-14 60,1.53477231e-14 C61,1.27897692e-14 62,1 62,2 C62,2 62,12 62,12 C62,13 61,14 60,14 C60,14 42,14 42,14 C41,14 40,13 40,12 C40,12 40,2 40,2 Z M44,3.5 C44,3.5 44,10.5 44,10.5 C44,11 44.5,11.5 45,11.5 C45,11.5 57,11.5 57,11.5 C57.5,11.5 58,11 58,10.5 C58,10.5 58,3.5 58,3.5 C58,3 57.5,2.5 57,2.5 C57,2.5 45,2.5 45,2.5 C44.5,2.5 44,3 44,3.5 Z\"/>\n          <rect width=\"4\" height=\"2\" x=\"32\" y=\"6\" rx=\".5\"/>\n          <path d=\"M2,0 L26,0 C27,-2.04003481e-15 28,1 28,2 L28,14 L24,14 L24,3.5 C24,3 23.5,2.5 23,2.5 L16,2.5 L16,14 L12,14 L12,2.5 L5,2.5 C4.5,2.5 4,3 4,3.5 L4,14 L0,14 L0,2 C0,1 1,-2.04003481e-15 2,0 Z\"/>\n        </g>\n      </svg>\n    </a>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'mo-logo',\n    props: {\n      width: {\n        type: Number,\n        default: 62\n      },\n      height: {\n        type: Number,\n        default: 14\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.logo {\n  display: inline-block;\n  margin: 0;\n  padding: 0;\n  > a {\n    display: block;\n    width: 100%;\n    height: 100%;\n    outline: none;\n    border: none;\n    text-align: center;\n    font-size: 0;\n    color: $--app-logo-color;\n  }\n  svg {\n    fill: currentColor;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Logo/LogoMini.vue",
    "content": "<template>\n  <h1 class=\"logo-mini\">\n    <a target=\"_blank\" href=\"https://motrix.app/\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 28 14\">\n        <path fill=\"#FFF\" fill-rule=\"evenodd\" d=\"M4,9 L28,9 C29,9 30,10 30,11 L30,23 L26,23 L26,12.5 C26,12 25.5,11.5 25,11.5 L18,11.5 L18,23 L14,23 L14,11.5 L7,11.5 C6.5,11.5 6,12 6,12.5 L6,23 L2,23 L2,11 C2,10 3,9 4,9 Z\" transform=\"translate(-2 -9)\"/>\n      </svg>\n    </a>\n  </h1>\n</template>\n\n<script>\n  export default {\n    name: 'mo-logo-mini'\n  }\n</script>\n\n<style lang=\"scss\">\n.logo-mini {\n  margin: 0;\n  padding: 0;\n  width: 100%;\n  > a {\n    display: block;\n    width: 28px;\n    height: 28px;\n    text-align: center;\n    font-size: 0;\n    outline: none;\n    padding: 2px;\n    margin: 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Main.vue",
    "content": "<template>\n  <el-container id=\"container\">\n    <mo-aside />\n    <router-view />\n    <mo-speedometer />\n    <mo-add-task :visible=\"addTaskVisible\" :type=\"addTaskType\" />\n    <mo-about-panel :visible=\"aboutPanelVisible\" />\n    <mo-task-detail\n      :visible=\"taskDetailVisible\"\n      :gid=\"currentTaskGid\"\n      :task=\"currentTaskItem\"\n      :files=\"currentTaskFiles\"\n      :peers=\"currentTaskPeers\"\n    />\n    <mo-dragger />\n  </el-container>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import AboutPanel from '@/components/About/AboutPanel'\n  import Aside from '@/components/Aside/Index'\n  import Speedometer from '@/components/Speedometer/Speedometer'\n  import AddTask from '@/components/Task/AddTask'\n  import TaskDetail from '@/components/TaskDetail/Index'\n  import Dragger from '@/components/Dragger/Index'\n\n  export default {\n    name: 'mo-main',\n    components: {\n      [AboutPanel.name]: AboutPanel,\n      [Aside.name]: Aside,\n      [Speedometer.name]: Speedometer,\n      [AddTask.name]: AddTask,\n      [TaskDetail.name]: TaskDetail,\n      [Dragger.name]: Dragger\n    },\n    computed: {\n      ...mapState('app', {\n        aboutPanelVisible: state => state.aboutPanelVisible,\n        addTaskVisible: state => state.addTaskVisible,\n        addTaskType: state => state.addTaskType\n      }),\n      ...mapState('task', {\n        taskDetailVisible: state => state.taskDetailVisible,\n        currentTaskGid: state => state.currentTaskGid,\n        currentTaskItem: state => state.currentTaskItem,\n        currentTaskFiles: state => state.currentTaskFiles,\n        currentTaskPeers: state => state.currentTaskPeers\n      })\n    },\n    methods: {\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n  .mo-speedometer {\n    position: fixed;\n    right: 16px;\n    bottom: 24px;\n    z-index: 20;\n  }\n</style>\n"
  },
  {
    "path": "src/renderer/components/Msg/index.js",
    "content": "const queue = []\nconst maxLength = 5\n\nexport default {\n  install: function (Vue, Message, defaultOption = {}) {\n    Vue.prototype.$msg = new Proxy(Message, {\n      get (obj, prop) {\n        return (arg) => {\n          if (!(arg instanceof Object)) {\n            arg = { message: arg }\n          }\n          const task = {\n            run () {\n              obj[prop]({\n                ...defaultOption,\n                ...arg,\n                onClose (...data) {\n                  const currentTask = queue.pop()\n                  if (currentTask) {\n                    currentTask.run()\n                  }\n                  if (arg.onClose) {\n                    arg.onClose(...data)\n                  }\n                }\n              })\n            }\n          }\n\n          if (queue.length >= maxLength) {\n            queue.pop()\n          }\n          queue.unshift(task)\n\n          if (queue.length === 1) {\n            queue.pop().run()\n          }\n        }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "src/renderer/components/Native/DynamicTray.vue",
    "content": "<template>\n  <div style=\"display: none;\">\n    <img\n      id=\"tray-icon-light-normal\"\n      src=\"static/mo-tray-light-normal@2x.png\"\n    >\n    <img\n      id=\"tray-icon-light-active\"\n      src=\"static/mo-tray-light-active@2x.png\"\n    >\n    <img\n      id=\"tray-icon-dark-normal\"\n      src=\"static/mo-tray-dark-normal@2x.png\"\n    >\n    <img\n      id=\"tray-icon-dark-active\"\n      src=\"static/mo-tray-dark-active@2x.png\"\n    >\n  </div>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n\n  import { getInverseTheme } from '@shared/utils'\n  import { APP_THEME } from '@shared/constants'\n\n  const cache = {}\n\n  export default {\n    name: 'mo-dynamic-tray',\n    computed: {\n      ...mapState('app', {\n        iconStatus: state => state.stat.numActive > 0 ? 'active' : 'normal',\n        theme: state => state.systemTheme,\n        focused: state => state.trayFocused,\n        uploadSpeed: state => state.stat.uploadSpeed,\n        downloadSpeed: state => state.stat.downloadSpeed,\n        speed: state => state.stat.uploadSpeed + state.stat.downloadSpeed\n      }),\n      scale () {\n        return 2\n      },\n      currentTheme () {\n        const { theme, focused } = this\n        if (theme === APP_THEME.DARK) {\n          return theme\n        }\n\n        return focused ? getInverseTheme(theme) : theme\n      },\n      iconKey () {\n        const { bigSur, iconStatus, currentTheme } = this\n        return bigSur ? 'tray-icon-light-normal' : `tray-icon-${currentTheme}-${iconStatus}`\n      }\n    },\n    watch: {\n      async speed (val) {\n        await this.drawTray()\n      },\n      async iconKey (val) {\n        await this.drawTray()\n      }\n    },\n    mounted () {\n      setTimeout(async () => {\n        await this.drawTray()\n      }, 200)\n    },\n    methods: {\n      async getIcon (key) {\n        if (cache[key]) {\n          return cache[key]\n        }\n\n        const iconImage = document.getElementById(key)\n        const result = await createImageBitmap(iconImage)\n        cache[key] = result\n\n        return result\n      },\n      async drawTray () {\n        const {\n          currentTheme: theme,\n          uploadSpeed,\n          downloadSpeed,\n          scale,\n          iconKey\n        } = this\n\n        const icon = await this.getIcon(iconKey)\n\n        global.app.trayWorker.postMessage({\n          type: 'tray:draw',\n          payload: {\n            theme,\n            icon,\n            uploadSpeed,\n            downloadSpeed,\n            scale\n          }\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Native/EngineClient.vue",
    "content": "<template>\n  <div v-if=\"false\"></div>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapState } from 'vuex'\n  import api from '@/api'\n  import {\n    getTaskFullPath,\n    showItemInFolder\n  } from '@/utils/native'\n  import { checkTaskIsBT, getTaskName } from '@shared/utils'\n\n  export default {\n    name: 'mo-engine-client',\n    computed: {\n      isRenderer: () => is.renderer(),\n      ...mapState('app', {\n        uploadSpeed: state => state.stat.uploadSpeed,\n        downloadSpeed: state => state.stat.downloadSpeed,\n        speed: state => state.stat.uploadSpeed + state.stat.downloadSpeed,\n        interval: state => state.interval,\n        downloading: state => state.stat.numActive > 0,\n        progress: state => state.progress\n      }),\n      ...mapState('task', {\n        messages: state => state.messages,\n        seedingList: state => state.seedingList,\n        taskDetailVisible: state => state.taskDetailVisible,\n        enabledFetchPeers: state => state.enabledFetchPeers,\n        currentTaskGid: state => state.currentTaskGid,\n        currentTaskItem: state => state.currentTaskItem\n      }),\n      ...mapState('preference', {\n        taskNotification: state => state.config.taskNotification\n      }),\n      currentTaskIsBT () {\n        return checkTaskIsBT(this.currentTaskItem)\n      }\n    },\n    watch: {\n      speed (val) {\n        const { uploadSpeed, downloadSpeed } = this\n        this.$electron.ipcRenderer.send('event', 'speed-change', {\n          uploadSpeed,\n          downloadSpeed\n        })\n      },\n      downloading (val, oldVal) {\n        if (val !== oldVal && this.isRenderer) {\n          this.$electron.ipcRenderer.send('event', 'download-status-change', val)\n        }\n      },\n      progress (val) {\n        this.$electron.ipcRenderer.send('event', 'progress-change', val)\n      }\n    },\n    methods: {\n      async fetchTaskItem ({ gid }) {\n        return api.fetchTaskItem({ gid })\n          .catch((e) => {\n            console.warn(`fetchTaskItem fail: ${e.message}`)\n          })\n      },\n      onDownloadStart (event) {\n        this.$store.dispatch('task/fetchList')\n        this.$store.dispatch('app/resetInterval')\n        this.$store.dispatch('task/saveSession')\n        const [{ gid }] = event\n        const { seedingList } = this\n        if (seedingList.includes(gid)) {\n          return\n        }\n\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            const { dir } = task\n            this.$store.dispatch('preference/recordHistoryDirectory', dir)\n            const taskName = getTaskName(task)\n            const message = this.$t('task.download-start-message', { taskName })\n            this.$msg.info(message)\n          })\n      },\n      onDownloadPause (event) {\n        const [{ gid }] = event\n        const { seedingList } = this\n        if (seedingList.includes(gid)) {\n          return\n        }\n\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            const taskName = getTaskName(task)\n            const message = this.$t('task.download-pause-message', { taskName })\n            this.$msg.info(message)\n          })\n      },\n      onDownloadStop (event) {\n        const [{ gid }] = event\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            const taskName = getTaskName(task)\n            const message = this.$t('task.download-stop-message', { taskName })\n            this.$msg.info(message)\n          })\n      },\n      onDownloadError (event) {\n        const [{ gid }] = event\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            const taskName = getTaskName(task)\n            const { errorCode, errorMessage } = task\n            console.error(`[Motrix] download error gid: ${gid}, #${errorCode}, ${errorMessage}`)\n            const message = this.$t('task.download-error-message', { taskName })\n            const link = `<a target=\"_blank\" href=\"https://github.com/agalwood/Motrix/wiki/Error#${errorCode}\" rel=\"noopener noreferrer\">${errorCode}</a>`\n            this.$msg({\n              type: 'error',\n              showClose: true,\n              duration: 5000,\n              dangerouslyUseHTMLString: true,\n              message: `${message} ${link}`\n            })\n          })\n      },\n      onDownloadComplete (event) {\n        this.$store.dispatch('task/fetchList')\n        const [{ gid }] = event\n        this.$store.dispatch('task/removeFromSeedingList', gid)\n\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            this.handleDownloadComplete(task, false)\n          })\n      },\n      onBtDownloadComplete (event) {\n        this.$store.dispatch('task/fetchList')\n        const [{ gid }] = event\n        const { seedingList } = this\n        if (seedingList.includes(gid)) {\n          return\n        }\n\n        this.$store.dispatch('task/addToSeedingList', gid)\n\n        this.fetchTaskItem({ gid })\n          .then((task) => {\n            this.handleDownloadComplete(task, true)\n          })\n      },\n      handleDownloadComplete (task, isBT) {\n        this.$store.dispatch('task/saveSession')\n\n        const path = getTaskFullPath(task)\n        this.showTaskCompleteNotify(task, isBT, path)\n        this.$electron.ipcRenderer.send('event', 'task-download-complete', task, path)\n      },\n      showTaskCompleteNotify (task, isBT, path) {\n        const taskName = getTaskName(task)\n        const message = isBT\n          ? this.$t('task.bt-download-complete-message', { taskName })\n          : this.$t('task.download-complete-message', { taskName })\n        const tips = isBT\n          ? '\\n' + this.$t('task.bt-download-complete-tips')\n          : ''\n\n        this.$msg.success(`${message}${tips}`)\n\n        if (!this.taskNotification) {\n          return\n        }\n\n        const notifyMessage = isBT\n          ? this.$t('task.bt-download-complete-notify')\n          : this.$t('task.download-complete-notify')\n\n        /* eslint-disable no-new */\n        const notify = new Notification(notifyMessage, {\n          body: `${taskName}${tips}`\n        })\n        notify.onclick = () => {\n          showItemInFolder(path, {\n            errorMsg: this.$t('task.file-not-exist')\n          })\n        }\n      },\n      showTaskErrorNotify (task) {\n        const taskName = getTaskName(task)\n\n        const message = this.$t('task.download-fail-message', { taskName })\n        this.$msg.success(message)\n\n        if (!this.taskNotification) {\n          return\n        }\n\n        /* eslint-disable no-new */\n        new Notification(this.$t('task.download-fail-notify'), {\n          body: taskName\n        })\n      },\n      bindEngineEvents () {\n        api.client.on('onDownloadStart', this.onDownloadStart)\n        // api.client.on('onDownloadPause', this.onDownloadPause)\n        api.client.on('onDownloadStop', this.onDownloadStop)\n        api.client.on('onDownloadComplete', this.onDownloadComplete)\n        api.client.on('onDownloadError', this.onDownloadError)\n        api.client.on('onBtDownloadComplete', this.onBtDownloadComplete)\n      },\n      unbindEngineEvents () {\n        api.client.removeListener('onDownloadStart', this.onDownloadStart)\n        // api.client.removeListener('onDownloadPause', this.onDownloadPause)\n        api.client.removeListener('onDownloadStop', this.onDownloadStop)\n        api.client.removeListener('onDownloadComplete', this.onDownloadComplete)\n        api.client.removeListener('onDownloadError', this.onDownloadError)\n        api.client.removeListener('onBtDownloadComplete', this.onBtDownloadComplete)\n      },\n      startPolling () {\n        this.timer = setTimeout(() => {\n          this.polling()\n          this.startPolling()\n        }, this.interval)\n      },\n      polling () {\n        this.$store.dispatch('app/fetchGlobalStat')\n        this.$store.dispatch('app/fetchProgress')\n        this.$store.dispatch('task/fetchList')\n\n        if (this.taskDetailVisible && this.currentTaskGid) {\n          if (this.currentTaskIsBT && this.enabledFetchPeers) {\n            this.$store.dispatch('task/fetchItemWithPeers', this.currentTaskGid)\n          } else {\n            this.$store.dispatch('task/fetchItem', this.currentTaskGid)\n          }\n        }\n      },\n      stopPolling () {\n        clearTimeout(this.timer)\n        this.timer = null\n      }\n    },\n    created () {\n      this.bindEngineEvents()\n    },\n    mounted () {\n      setTimeout(() => {\n        this.$store.dispatch('app/fetchEngineInfo')\n        this.$store.dispatch('app/fetchEngineOptions')\n\n        this.startPolling()\n      }, 100)\n    },\n    destroyed () {\n      this.$store.dispatch('task/saveSession')\n\n      this.unbindEngineEvents()\n\n      this.stopPolling()\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Native/Ipc.vue",
    "content": "<template>\n  <div v-if=\"false\"></div>\n</template>\n\n<script>\n  import { commands } from '@/components/CommandManager/instance'\n\n  export default {\n    name: 'mo-ipc',\n    methods: {\n      bindIpcEvents () {\n        this.$electron.ipcRenderer.on('command', (event, command, ...args) => {\n          commands.execute(command, ...args)\n        })\n      },\n      unbindIpcEvents () {\n        this.$electron.ipcRenderer.removeAllListeners('command')\n      }\n    },\n    created () {\n      this.bindIpcEvents()\n    },\n    destroyed () {\n      this.unbindIpcEvents()\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Native/SelectDirectory.vue",
    "content": "<template>\n  <el-button\n    class=\"select-directory\"\n    @click.stop=\"onFolderClick\"\n  >\n    <mo-icon name=\"folder\" width=\"10\" height=\"10\" />\n  </el-button>\n</template>\n\n<script>\n  import { dialog } from '@electron/remote'\n  import '@/components/Icons/folder'\n\n  export default {\n    name: 'mo-select-directory',\n    props: {\n    },\n    methods: {\n      onFolderClick () {\n        const self = this\n        dialog.showOpenDialog({\n          properties: ['openDirectory', 'createDirectory']\n        }).then(({ canceled, filePaths }) => {\n          if (canceled || filePaths.length === 0) {\n            return\n          }\n\n          const [path] = filePaths\n          self.$emit('selected', path)\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Native/ShowInFolder.vue",
    "content": "<template>\n  <i @click.stop=\"onFolderClick\">\n    <mo-icon name=\"folder\" width=\"10\" height=\"10\" />\n  </i>\n</template>\n\n<script>\n  import '@/components/Icons/folder'\n  import {\n    showItemInFolder\n  } from '@/utils/native'\n\n  export default {\n    name: 'mo-show-in-folder',\n    props: {\n      path: {\n        type: String\n      }\n    },\n    computed: {\n    },\n    methods: {\n      onFolderClick () {\n        if (!this.path) {\n          return\n        }\n        showItemInFolder(this.path, {\n          errorMsg: this.$t('task.file-not-exist')\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Native/TitleBar.vue",
    "content": "<template>\n  <div class=\"title-bar\">\n    <div class=\"title-bar-dragger\"></div>\n    <ul v-if=\"showActions\" class=\"window-actions\">\n      <li @click=\"handleMinimize\">\n        <mo-icon name=\"win-minimize\" width=\"12\" height=\"12\" />\n      </li>\n      <li @click=\"handleMaximize\">\n        <mo-icon name=\"win-maximize\" width=\"12\" height=\"12\" />\n      </li>\n      <li @click=\"handleClose\" class=\"win-close-btn\">\n        <mo-icon name=\"win-close\" width=\"12\" height=\"12\" />\n      </li>\n    </ul>\n  </div>\n</template>\n\n<script>\n  import { getCurrentWindow } from '@electron/remote'\n  import '@/components/Icons/win-minimize'\n  import '@/components/Icons/win-maximize'\n  import '@/components/Icons/win-close'\n\n  export default {\n    name: 'mo-title-bar',\n    props: {\n      showActions: {\n        type: Boolean\n      }\n    },\n    computed: {\n      win () {\n        return getCurrentWindow()\n      }\n    },\n    methods: {\n      handleMinimize () {\n        this.win.minimize()\n      },\n      handleMaximize () {\n        if (this.win.isMaximized()) {\n          this.win.unmaximize()\n        } else {\n          this.win.maximize()\n        }\n      },\n      handleClose () {\n        this.win.close()\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.title-bar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  display: flex;\n  flex-direction: row;\n  width: 100%;\n  height: 36px;\n  z-index: 5000;\n  .title-bar-dragger {\n    margin: 5px 0 0 5px;\n    flex: 1;\n    user-select: none;\n    -webkit-app-region: drag;\n    -webkit-user-select: none;\n  }\n  .window-actions {\n    opacity: 0.4;\n    transition: $--fade-transition;\n    list-style: none;\n    padding: 0;\n    margin: 0;\n    z-index: 5100;\n    font-size: 0;\n    > li {\n      display: inline-block;\n      padding: 5px 18px;\n      font-size: 16px;\n      margin: 0;\n      color: $--titlebar-actions-color;\n      &:hover {\n        background-color: $--titlebar-actions-active-background;\n      }\n      &.win-close-btn:hover {\n        color: $--titlebar-close-active-color;\n        background-color: $--titlebar-close-active-background;\n      }\n    }\n  }\n  &:hover {\n    .window-actions {\n      opacity: 1;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Preference/Advanced.vue",
    "content": "<template>\n  <el-container class=\"content panel\" direction=\"vertical\">\n    <el-header class=\"panel-header\" height=\"84\">\n      <h4 class=\"hidden-xs-only\">{{ title }}</h4>\n      <mo-subnav-switcher\n        :title=\"title\"\n        :subnavs=\"subnavs\"\n        class=\"hidden-sm-and-up\"\n      />\n    </el-header>\n    <el-main class=\"panel-content\">\n      <el-form\n        class=\"form-preference\"\n        ref=\"advancedForm\"\n        label-position=\"right\"\n        size=\"mini\"\n        :model=\"form\"\n        :rules=\"rules\"\n      >\n        <el-form-item\n          :label=\"`${$t('preferences.auto-update')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.autoCheckUpdate\">\n              {{ $t('preferences.auto-check-update') }}\n            </el-checkbox>\n            <div\n              class=\"el-form-item__info\"\n              style=\"margin-top: 8px;\"\n              v-if=\"form.lastCheckUpdateTime !== 0\"\n            >\n              {{\n                $t('preferences.last-check-update-time') + ': ' +\n                (\n                  form.lastCheckUpdateTime !== 0 ?\n                    new Date(form.lastCheckUpdateTime).toLocaleString() :\n                    new Date().toLocaleString()\n                )\n              }}\n              <span class=\"action-link\" @click.prevent=\"onCheckUpdateClick\">\n                {{ $t('app.check-updates-now') }}\n              </span>\n            </div>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.proxy')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-switch\n            v-model=\"form.proxy.enable\"\n            :active-text=\"$t('preferences.enable-proxy')\"\n            @change=\"onProxyEnableChange\"\n            >\n          </el-switch>\n        </el-form-item>\n        <el-form-item\n          :label-width=\"formLabelWidth\"\n          v-if=\"form.proxy.enable\"\n          style=\"margin-top: -16px;\"\n        >\n          <el-col\n            class=\"form-item-sub\"\n            :xs=\"24\"\n            :sm=\"20\"\n            :md=\"16\"\n            :lg=\"16\"\n          >\n            <el-input\n              placeholder=\"[http://][USER:PASSWORD@]HOST[:PORT]\"\n              @change=\"onProxyServerChange\"\n              v-model=\"form.proxy.server\">\n            </el-input>\n          </el-col>\n          <el-col\n            class=\"form-item-sub\"\n            :xs=\"24\"\n            :sm=\"24\"\n            :md=\"20\"\n            :lg=\"20\"\n          >\n            <el-input\n              type=\"textarea\"\n              rows=\"2\"\n              auto-complete=\"off\"\n              @change=\"handleProxyBypassChange\"\n              :placeholder=\"`${$t('preferences.proxy-bypass-input-tips')}`\"\n              v-model=\"form.proxy.bypass\">\n            </el-input>\n          </el-col>\n          <el-col\n            class=\"form-item-sub\"\n            :xs=\"24\"\n            :sm=\"24\"\n            :md=\"20\"\n            :lg=\"20\"\n          >\n            <el-select\n              class=\"proxy-scope\"\n              v-model=\"form.proxy.scope\"\n              multiple\n            >\n              <el-option\n                v-for=\"item in proxyScopeOptions\"\n                :key=\"item\"\n                :label=\"$t(`preferences.proxy-scope-${item}`)\"\n                :value=\"item\"\n              />\n            </el-select>\n            <div class=\"el-form-item__info\" style=\"margin-top: 8px;\">\n              <a target=\"_blank\" href=\"https://github.com/agalwood/Motrix/wiki/Proxy\" rel=\"noopener noreferrer\">\n                {{ $t('preferences.proxy-tips') }}\n                <mo-icon name=\"link\" width=\"12\" height=\"12\" />\n              </a>\n            </div>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.bt-tracker')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <div class=\"form-item-sub bt-tracker\">\n            <el-row :gutter=\"10\" style=\"line-height: 0;\">\n              <el-col :span=\"20\">\n                <div class=\"track-source\">\n                  <el-select\n                    class=\"select-track-source\"\n                    v-model=\"form.trackerSource\"\n                    allow-create\n                    filterable\n                    multiple\n                  >\n                    <el-option-group\n                      v-for=\"group in trackerSourceOptions\"\n                      :key=\"group.label\"\n                      :label=\"group.label\"\n                    >\n                      <el-option\n                        v-for=\"item in group.options\"\n                        :key=\"item.value\"\n                        :label=\"item.label\"\n                        :value=\"item.value\"\n                      >\n                        <span style=\"float: left\">{{ item.label }}</span>\n                        <span style=\"float: right; margin-right: 24px\">\n                          <el-tag\n                            type=\"success\"\n                            size=\"mini\"\n                            v-if=\"item.cdn\"\n                          >\n                            CDN\n                          </el-tag>\n                        </span>\n                      </el-option>\n                    </el-option-group>\n                  </el-select>\n                </div>\n              </el-col>\n              <el-col :span=\"3\">\n                <div class=\"sync-tracker\">\n                  <el-tooltip\n                    class=\"item\"\n                    effect=\"dark\"\n                    :content=\"$t('preferences.sync-tracker-tips')\"\n                    placement=\"bottom\"\n                  >\n                    <el-button\n                      @click=\"syncTrackerFromSource\"\n                      class=\"sync-tracker-btn\"\n                    >\n                      <mo-icon\n                        name=\"refresh\"\n                        width=\"12\"\n                        height=\"12\"\n                        :spin=\"true\"\n                        v-if=\"trackerSyncing\"\n                      />\n                      <mo-icon name=\"sync\" width=\"12\" height=\"12\" v-else />\n                    </el-button>\n                  </el-tooltip>\n                </div>\n              </el-col>\n            </el-row>\n            <el-input\n              type=\"textarea\"\n              rows=\"3\"\n              auto-complete=\"off\"\n              :placeholder=\"`${$t('preferences.bt-tracker-input-tips')}`\"\n              v-model=\"form.btTracker\">\n            </el-input>\n            <div class=\"el-form-item__info\" style=\"margin-top: 8px;\">\n              {{ $t('preferences.bt-tracker-tips') }}\n              <a target=\"_blank\" href=\"https://github.com/ngosang/trackerslist\" rel=\"noopener noreferrer\">\n                ngosang/trackerslist\n                <mo-icon name=\"link\" width=\"12\" height=\"12\" />\n              </a>\n              <a target=\"_blank\" href=\"https://github.com/XIU2/TrackersListCollection\" rel=\"noopener noreferrer\">\n                XIU2/TrackersListCollection\n                <mo-icon name=\"link\" width=\"12\" height=\"12\" />\n              </a>\n            </div>\n          </div>\n          <div class=\"form-item-sub\">\n            <el-checkbox v-model=\"form.autoSyncTracker\">\n              {{ $t('preferences.auto-sync-tracker') }}\n            </el-checkbox>\n            <div class=\"el-form-item__info\" style=\"margin-top: 8px;\" v-if=\"form.lastSyncTrackerTime > 0\">\n              {{ new Date(form.lastSyncTrackerTime).toLocaleString() }}\n            </div>\n          </div>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.rpc')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-row style=\"margin-bottom: 8px;\">\n            <el-col\n              class=\"form-item-sub\"\n              :xs=\"24\"\n              :sm=\"18\"\n              :md=\"10\"\n              :lg=\"10\"\n            >\n              {{ $t('preferences.rpc-listen-port') }}\n              <el-input\n                :placeholder=\"rpcDefaultPort\"\n                :maxlength=\"8\"\n                v-model=\"form.rpcListenPort\"\n                @change=\"onRpcListenPortChange\"\n              >\n                <i slot=\"append\" @click.prevent=\"onRpcPortDiceClick\">\n                  <mo-icon name=\"dice\" width=\"12\" height=\"12\" />\n                </i>\n              </el-input>\n            </el-col>\n          </el-row>\n          <el-row style=\"margin-bottom: 8px;\">\n            <el-col\n              class=\"form-item-sub\"\n              :xs=\"24\"\n              :sm=\"18\"\n              :md=\"18\"\n              :lg=\"18\"\n            >\n              {{ $t('preferences.rpc-secret') }}\n              <el-input\n                :show-password=\"hideRpcSecret\"\n                placeholder=\"RPC Secret\"\n                :maxlength=\"64\"\n                v-model=\"form.rpcSecret\"\n              >\n                <i slot=\"append\" @click.prevent=\"onRpcSecretDiceClick\">\n                  <mo-icon name=\"dice\" width=\"12\" height=\"12\" />\n                </i>\n              </el-input>\n              <div class=\"el-form-item__info\" style=\"margin-top: 8px;\">\n                <a target=\"_blank\" href=\"https://github.com/agalwood/Motrix/wiki/RPC\" rel=\"noopener noreferrer\">\n                  {{ $t('preferences.rpc-secret-tips') }}\n                  <mo-icon name=\"link\" width=\"12\" height=\"12\" />\n                </a>\n              </div>\n            </el-col>\n          </el-row>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.port')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-row style=\"margin-bottom: 8px;\">\n            <el-col\n              class=\"form-item-sub\"\n              :xs=\"24\"\n              :sm=\"18\"\n              :md=\"12\"\n              :lg=\"12\"\n            >\n              <el-switch\n                v-model=\"form.enableUpnp\"\n                active-text=\"UPnP/NAT-PMP\"\n                >\n              </el-switch>\n            </el-col>\n          </el-row>\n          <el-row style=\"margin-bottom: 8px;\">\n            <el-col class=\"form-item-sub\"\n              :xs=\"24\"\n              :sm=\"18\"\n              :md=\"10\"\n              :lg=\"10\"\n            >\n              {{ $t('preferences.bt-port') }}\n              <el-input\n                placeholder=\"BT Port\"\n                :maxlength=\"8\"\n                v-model=\"form.listenPort\"\n              >\n                <i slot=\"append\" @click.prevent=\"onBtPortDiceClick\">\n                  <mo-icon name=\"dice\" width=\"12\" height=\"12\" />\n                </i>\n              </el-input>\n            </el-col>\n          </el-row>\n          <el-row>\n            <el-col\n              class=\"form-item-sub\"\n              :xs=\"24\"\n              :sm=\"18\"\n              :md=\"10\"\n              :lg=\"10\"\n            >\n              {{ $t('preferences.dht-port') }}\n              <el-input\n                placeholder=\"DHT Port\"\n                :maxlength=\"8\"\n                v-model=\"form.dhtListenPort\"\n              >\n                <i slot=\"append\" @click.prevent=\"onDhtPortDiceClick\">\n                  <mo-icon name=\"dice\" width=\"12\" height=\"12\" />\n                </i>\n              </el-input>\n            </el-col>\n          </el-row>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.download-protocol')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          {{ $t('preferences.protocols-default-client') }}\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-switch\n              v-model=\"form.protocols.magnet\"\n              :active-text=\"$t('preferences.protocols-magnet')\"\n              @change=\"(val) => onProtocolsChange('magnet', val)\"\n              >\n            </el-switch>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-switch\n              v-model=\"form.protocols.thunder\"\n              :active-text=\"$t('preferences.protocols-thunder')\"\n              @change=\"(val) => onProtocolsChange('thunder', val)\"\n              >\n            </el-switch>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.user-agent')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.mock-user-agent') }}\n            <el-input\n              type=\"textarea\"\n              rows=\"2\"\n              auto-complete=\"off\"\n              placeholder=\"User-Agent\"\n              v-model=\"form.userAgent\">\n            </el-input>\n            <el-button-group class=\"ua-group\">\n              <el-button @click=\"() => changeUA('aria2')\">Aria2</el-button>\n              <el-button @click=\"() => changeUA('transmission')\">Transmission</el-button>\n              <el-button @click=\"() => changeUA('chrome')\">Chrome</el-button>\n              <el-button @click=\"() => changeUA('du')\">du</el-button>\n            </el-button-group>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.developer')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.aria2-conf-path') }}\n            <el-input placeholder=\"\" disabled v-model=\"aria2ConfPath\">\n              <mo-show-in-folder\n                slot=\"append\"\n                v-if=\"isRenderer\"\n                :path=\"aria2ConfPath\"\n              />\n            </el-input>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.download-session-path') }}\n            <el-input placeholder=\"\" disabled v-model=\"sessionPath\">\n              <mo-show-in-folder\n                slot=\"append\"\n                v-if=\"isRenderer\"\n                :path=\"sessionPath\"\n              />\n            </el-input>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.app-log-path') }}\n            <el-row :gutter=\"16\">\n              <el-col :span=\"18\">\n                <el-input placeholder=\"\" disabled v-model=\"logPath\">\n                  <mo-show-in-folder\n                  slot=\"append\"\n                  v-if=\"isRenderer\"\n                  :path=\"logPath\"\n                  />\n                </el-input>\n              </el-col>\n              <el-col :span=\"6\">\n                <el-select v-model=\"form.logLevel\">\n                  <el-option\n                    v-for=\"item in logLevels\"\n                    :key=\"item\"\n                    :label=\"item\"\n                    :value=\"item\">\n                  </el-option>\n                </el-select>\n              </el-col>\n            </el-row>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-button plain type=\"warning\" @click=\"() => onSessionResetClick()\">\n              {{ $t('preferences.session-reset') }}\n            </el-button>\n            <el-button plain type=\"danger\" @click=\"() => onFactoryResetClick()\">\n              {{ $t('preferences.factory-reset') }}\n            </el-button>\n          </el-col>\n        </el-form-item>\n      </el-form>\n      <div class=\"form-actions\">\n        <el-button\n          type=\"primary\"\n          @click=\"submitForm('advancedForm')\"\n        >\n          {{ $t('preferences.save') }}\n        </el-button>\n        <el-button\n          @click=\"resetForm('advancedForm')\"\n        >\n          {{ $t('preferences.discard') }}\n        </el-button>\n      </div>\n    </el-main>\n  </el-container>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { dialog } from '@electron/remote'\n  import { mapState } from 'vuex'\n  import { cloneDeep, extend, isEmpty } from 'lodash'\n  import randomize from 'randomatic'\n  import ShowInFolder from '@/components/Native/ShowInFolder'\n  import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'\n  import userAgentMap from '@shared/ua'\n  import {\n    EMPTY_STRING,\n    ENGINE_RPC_PORT,\n    LOG_LEVELS,\n    TRACKER_SOURCE_OPTIONS,\n    PROXY_SCOPE_OPTIONS\n  } from '@shared/constants'\n  import {\n    buildRpcUrl,\n    calcFormLabelWidth,\n    changedConfig,\n    checkIsNeedRestart,\n    convertCommaToLine,\n    convertLineToComma,\n    diffConfig,\n    generateRandomInt\n  } from '@shared/utils'\n  import { convertTrackerDataToLine, reduceTrackerString } from '@shared/utils/tracker'\n  import '@/components/Icons/dice'\n  import '@/components/Icons/sync'\n  import '@/components/Icons/refresh'\n  import { getLanguage } from '@shared/locales'\n  import { getLocaleManager } from '@/components/Locale'\n\n  const initForm = (config) => {\n    const {\n      autoCheckUpdate,\n      autoSyncTracker,\n      btTracker,\n      dhtListenPort,\n      enableUpnp,\n      hideAppMenu,\n      lastCheckUpdateTime,\n      lastSyncTrackerTime,\n      listenPort,\n      logLevel,\n      protocols,\n      proxy,\n      rpcListenPort,\n      rpcSecret,\n      trackerSource,\n      useProxy,\n      userAgent\n    } = config\n    const result = {\n      autoCheckUpdate,\n      autoSyncTracker,\n      btTracker: convertCommaToLine(btTracker),\n      dhtListenPort,\n      enableUpnp,\n      hideAppMenu,\n      lastCheckUpdateTime,\n      lastSyncTrackerTime,\n      listenPort,\n      logLevel,\n      proxy: cloneDeep(proxy),\n      protocols: { ...protocols },\n      rpcListenPort,\n      rpcSecret,\n      trackerSource,\n      useProxy,\n      userAgent\n    }\n    return result\n  }\n\n  export default {\n    name: 'mo-preference-advanced',\n    components: {\n      [SubnavSwitcher.name]: SubnavSwitcher,\n      [ShowInFolder.name]: ShowInFolder\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      const formOriginal = initForm(this.$store.state.preference.config)\n      let form = {}\n      form = initForm(extend(form, formOriginal, changedConfig.advanced))\n\n      return {\n        form,\n        formLabelWidth: calcFormLabelWidth(locale),\n        formOriginal,\n        hideRpcSecret: true,\n        proxyScopeOptions: PROXY_SCOPE_OPTIONS,\n        rules: {},\n        trackerSourceOptions: TRACKER_SOURCE_OPTIONS,\n        trackerSyncing: false\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      title () {\n        return this.$t('preferences.advanced')\n      },\n      subnavs () {\n        return [\n          {\n            key: 'basic',\n            title: this.$t('preferences.basic'),\n            route: '/preference/basic'\n          },\n          {\n            key: 'advanced',\n            title: this.$t('preferences.advanced'),\n            route: '/preference/advanced'\n          },\n          {\n            key: 'lab',\n            title: this.$t('preferences.lab'),\n            route: '/preference/lab'\n          }\n        ]\n      },\n      rpcDefaultPort () {\n        return ENGINE_RPC_PORT\n      },\n      logLevels () {\n        return LOG_LEVELS\n      },\n      ...mapState('preference', {\n        config: state => state.config,\n        aria2ConfPath: state => state.config.aria2ConfPath,\n        logPath: state => state.config.logPath,\n        sessionPath: state => state.config.sessionPath\n      })\n    },\n    watch: {\n      'form.rpcListenPort' (val) {\n        const url = buildRpcUrl({\n          port: this.form.rpcListenPort,\n          secret: val\n        })\n        navigator.clipboard.writeText(url)\n      },\n      'form.rpcSecret' (val) {\n        const url = buildRpcUrl({\n          port: this.form.rpcListenPort,\n          secret: val\n        })\n        navigator.clipboard.writeText(url)\n      }\n    },\n    methods: {\n      handleLocaleChange (locale) {\n        const lng = getLanguage(locale)\n        getLocaleManager().changeLanguage(lng)\n      },\n      onCheckUpdateClick () {\n        this.$electron.ipcRenderer.send('command', 'application:check-for-updates')\n        this.$msg.info(this.$t('app.checking-for-updates'))\n        this.$store.dispatch('preference/fetchPreference')\n          .then((config) => {\n            const { lastCheckUpdateTime } = config\n            this.form.lastCheckUpdateTime = lastCheckUpdateTime\n          })\n      },\n      syncTrackerFromSource () {\n        this.trackerSyncing = true\n        const { trackerSource } = this.form\n        this.$store.dispatch('preference/fetchBtTracker', trackerSource)\n          .then((data) => {\n            const tracker = convertTrackerDataToLine(data)\n            this.form.lastSyncTrackerTime = Date.now()\n            this.form.btTracker = tracker\n            this.trackerSyncing = false\n          })\n          .catch((_) => {\n            this.trackerSyncing = false\n          })\n      },\n      onProtocolsChange (protocol, enabled) {\n        const { protocols } = this.form\n        this.form.protocols = {\n          ...protocols,\n          [protocol]: enabled\n        }\n      },\n      onProxyEnableChange (enable) {\n        this.form.proxy = {\n          ...this.form.proxy,\n          enable\n        }\n      },\n      onProxyServerChange (server) {\n        this.form.proxy = {\n          ...this.form.proxy,\n          server\n        }\n      },\n      handleProxyBypassChange (bypass) {\n        this.form.proxy = {\n          ...this.form.proxy,\n          bypass: convertLineToComma(bypass)\n        }\n      },\n      onProxyScopeChange (scope) {\n        this.form.proxy = {\n          ...this.form.proxy,\n          scope: [...scope]\n        }\n      },\n      changeUA (type) {\n        const ua = userAgentMap[type]\n        if (!ua) {\n          return\n        }\n        this.form.userAgent = ua\n      },\n      onBtPortDiceClick () {\n        const port = generateRandomInt(20000, 24999)\n        this.form.listenPort = port\n      },\n      onDhtPortDiceClick () {\n        const port = generateRandomInt(25000, 29999)\n        this.form.dhtListenPort = port\n      },\n      onRpcListenPortChange (value) {\n        console.log('onRpcListenPortChange===>', value)\n        if (EMPTY_STRING === value) {\n          this.form.rpcListenPort = this.rpcDefaultPort\n        }\n      },\n      onRpcPortDiceClick () {\n        const port = generateRandomInt(ENGINE_RPC_PORT, 20000)\n        this.form.rpcListenPort = port\n      },\n      onRpcSecretDiceClick () {\n        this.hideRpcSecret = false\n        const rpcSecret = randomize('Aa0', 16)\n        this.form.rpcSecret = rpcSecret\n\n        setTimeout(() => {\n          this.hideRpcSecret = true\n        }, 2000)\n      },\n      onSessionResetClick () {\n        dialog.showMessageBox({\n          type: 'warning',\n          title: this.$t('preferences.session-reset'),\n          message: this.$t('preferences.session-reset-confirm'),\n          buttons: [this.$t('app.yes'), this.$t('app.no')],\n          cancelId: 1\n        }).then(({ response }) => {\n          if (response === 0) {\n            this.$store.dispatch('task/purgeTaskRecord')\n            this.$store.dispatch('task/pauseAllTask')\n              .then(() => {\n                this.$electron.ipcRenderer.send('command', 'application:reset-session')\n              })\n          }\n        })\n      },\n      onFactoryResetClick () {\n        dialog.showMessageBox({\n          type: 'warning',\n          title: this.$t('preferences.factory-reset'),\n          message: this.$t('preferences.factory-reset-confirm'),\n          buttons: [this.$t('app.yes'), this.$t('app.no')],\n          cancelId: 1\n        }).then(({ response }) => {\n          if (response === 0) {\n            this.$electron.ipcRenderer.send('command', 'application:factory-reset')\n          }\n        })\n      },\n      syncFormConfig () {\n        this.$store.dispatch('preference/fetchPreference')\n          .then((config) => {\n            this.form = initForm(config)\n            this.formOriginal = cloneDeep(this.form)\n          })\n      },\n      submitForm (formName) {\n        this.$refs[formName].validate((valid) => {\n          if (!valid) {\n            console.error('[Motrix] preference form valid:', valid)\n            return false\n          }\n\n          const data = {\n            ...diffConfig(this.formOriginal, this.form),\n            ...changedConfig.basic\n          }\n\n          const {\n            autoHideWindow,\n            btAutoDownloadContent,\n            btTracker,\n            rpcListenPort\n          } = data\n\n          if ('btAutoDownloadContent' in data) {\n            data.followTorrent = btAutoDownloadContent\n            data.followMetalink = btAutoDownloadContent\n            data.pauseMetadata = !btAutoDownloadContent\n          }\n\n          if (btTracker) {\n            data.btTracker = reduceTrackerString(convertLineToComma(btTracker))\n          }\n\n          if (rpcListenPort === EMPTY_STRING) {\n            data.rpcListenPort = this.rpcDefaultPort\n          }\n\n          console.log('[Motrix] preference changed data:', data)\n\n          this.$store.dispatch('preference/save', data)\n            .then(() => {\n              this.$store.dispatch('app/fetchEngineOptions')\n              this.syncFormConfig()\n              this.$msg.success(this.$t('preferences.save-success-message'))\n            })\n            .catch((e) => {\n              this.$msg.success(this.$t('preferences.save-fail-message'))\n            })\n\n          changedConfig.basic = {}\n          changedConfig.advanced = {}\n\n          if (this.isRenderer) {\n            if ('autoHideWindow' in data) {\n              this.$electron.ipcRenderer.send('command',\n                                              'application:auto-hide-window', autoHideWindow)\n            }\n\n            if (checkIsNeedRestart(data)) {\n              this.$electron.ipcRenderer.send('command', 'application:relaunch')\n            }\n          }\n        })\n      },\n      resetForm (formName) {\n        this.syncFormConfig()\n      }\n    },\n    beforeRouteLeave (to, from, next) {\n      changedConfig.advanced = diffConfig(this.formOriginal, this.form)\n      if (to.path === '/preference/basic') {\n        next()\n      } else {\n        if (isEmpty(changedConfig.basic) && isEmpty(changedConfig.advanced)) {\n          next()\n        } else {\n          dialog.showMessageBox({\n            type: 'warning',\n            title: this.$t('preferences.not-saved'),\n            message: this.$t('preferences.not-saved-confirm'),\n            buttons: [this.$t('app.yes'), this.$t('app.no')],\n            cancelId: 1\n          }).then(({ response }) => {\n            if (response === 0) {\n              changedConfig.basic = {}\n              changedConfig.advanced = {}\n              next()\n            }\n          })\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.proxy-scope {\n  width: 100%;\n}\n.bt-tracker {\n  position: relative;\n  .sync-tracker-btn {\n    line-height: 0;\n  }\n  .track-source {\n    margin-bottom: 16px;\n    .select-track-source {\n      width: 100%;\n    }\n    .el-select__tags {\n      overflow-x: auto;\n    }\n  }\n}\n.ua-group {\n  margin-top: 8px;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Preference/Basic.vue",
    "content": "<template>\n  <el-container class=\"content panel\" direction=\"vertical\">\n    <el-header class=\"panel-header\" height=\"84\">\n      <h4 class=\"hidden-xs-only\">{{ title }}</h4>\n      <mo-subnav-switcher\n        :title=\"title\"\n        :subnavs=\"subnavs\"\n        class=\"hidden-sm-and-up\"\n      />\n    </el-header>\n    <el-main class=\"panel-content\">\n      <el-form\n        class=\"form-preference\"\n        ref=\"basicForm\"\n        label-position=\"right\"\n        size=\"mini\"\n        :model=\"form\"\n        :rules=\"rules\"\n      >\n        <el-form-item\n          :label=\"`${$t('preferences.appearance')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <mo-theme-switcher\n              v-model=\"form.theme\"\n              @change=\"handleThemeChange\"\n              ref=\"themeSwitcher\"\n            />\n          </el-col>\n          <el-col v-if=\"showHideAppMenuOption\" class=\"form-item-sub\" :span=\"16\">\n            <el-checkbox v-model=\"form.hideAppMenu\">\n              {{ $t('preferences.hide-app-menu') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"16\">\n            <el-checkbox v-model=\"form.autoHideWindow\">\n              {{ $t('preferences.auto-hide-window') }}\n            </el-checkbox>\n          </el-col>\n          <el-col v-if=\"isMac\" class=\"form-item-sub\" :span=\"16\">\n            <el-checkbox v-model=\"form.traySpeedometer\">\n              {{ $t('preferences.tray-speedometer') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"16\">\n            <el-checkbox v-model=\"form.showProgressBar\">\n              {{ $t('preferences.show-progress-bar') }}\n            </el-checkbox>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          v-if=\"isMac\"\n          :label=\"`${$t('preferences.run-mode')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-select v-model=\"form.runMode\">\n              <el-option\n                v-for=\"item in runModes\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\">\n              </el-option>\n            </el-select>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.language')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"16\">\n            <el-select\n              v-model=\"form.locale\"\n              :placeholder=\"$t('preferences.change-language')\">\n              <el-option\n                v-for=\"item in locales\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\">\n              </el-option>\n            </el-select>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.startup')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col\n            class=\"form-item-sub\"\n            :span=\"24\"\n            v-if=\"!isLinux\"\n          >\n            <el-checkbox v-model=\"form.openAtLogin\">\n              {{ $t('preferences.open-at-login') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.keepWindowState\">\n              {{ $t('preferences.keep-window-state') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.resumeAllWhenAppLaunched\">\n              {{ $t('preferences.auto-resume-all') }}\n            </el-checkbox>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.default-dir')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-input placeholder=\"\" v-model=\"form.dir\" :readonly=\"isMas\">\n            <mo-history-directory\n              slot=\"prepend\"\n              @selected=\"handleHistoryDirectorySelected\"\n            />\n            <mo-select-directory\n              v-if=\"isRenderer\"\n              slot=\"append\"\n              @selected=\"handleNativeDirectorySelected\"\n            />\n          </el-input>\n          <div class=\"el-form-item__info\" v-if=\"isMas\" style=\"margin-top: 8px;\">\n            {{ $t('preferences.mas-default-dir-tips') }}\n          </div>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.transfer-settings')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.transfer-speed-upload') }}\n            <el-input-number\n              v-model=\"maxOverallUploadLimitParsed\"\n              controls-position=\"right\"\n              :min=\"0\"\n              :max=\"65535\"\n              :step=\"1\"\n              :label=\"$t('preferences.transfer-speed-download')\"\n              >\n            </el-input-number>\n            <el-select\n              style=\"width: 100px;\"\n              v-model=\"uploadUnit\"\n              @change=\"handleUploadChange\">\n              <el-option\n                v-for=\"item in speedUnits\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\">\n              </el-option>\n            </el-select>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.transfer-speed-download') }}\n            <el-input-number\n              v-model=\"maxOverallDownloadLimitParsed\"\n              controls-position=\"right\"\n              :min=\"0\"\n              :max=\"65535\"\n              :step=\"1\"\n              :label=\"$t('preferences.transfer-speed-download')\">\n            </el-input-number>\n            <el-select\n              style=\"width: 100px;\"\n              v-model=\"downloadUnit\"\n              @change=\"handleDownloadChange\">\n              <el-option\n                v-for=\"item in speedUnits\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\">\n              </el-option>\n            </el-select>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.bt-settings')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.btSaveMetadata\">\n              {{ $t('preferences.bt-save-metadata') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox\n              v-model=\"form.btAutoDownloadContent\"\n            >\n              {{ $t('preferences.bt-auto-download-content') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox\n              v-model=\"form.btForceEncryption\"\n            >\n              {{ $t('preferences.bt-force-encryption') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-switch\n              v-model=\"form.keepSeeding\"\n              :active-text=\"$t('preferences.keep-seeding')\"\n              @change=\"onKeepSeedingChange\"\n            >\n            </el-switch>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\" v-if=\"!form.keepSeeding\">\n            {{ $t('preferences.seed-ratio') }}\n            <el-input-number\n              v-model=\"form.seedRatio\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"100\"\n              :step=\"0.1\"\n              :label=\"$t('preferences.seed-ratio')\">\n            </el-input-number>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\" v-if=\"!form.keepSeeding\">\n            {{ $t('preferences.seed-time') }}\n            ({{ $t('preferences.seed-time-unit') }})\n            <el-input-number\n              v-model=\"form.seedTime\"\n              controls-position=\"right\"\n              :min=\"60\"\n              :max=\"525600\"\n              :step=\"1\"\n              :label=\"$t('preferences.seed-time')\">\n            </el-input-number>\n          </el-col>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('preferences.task-manage')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.max-concurrent-downloads') }}\n            <el-input-number\n              v-model=\"form.maxConcurrentDownloads\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"maxConcurrentDownloads\"\n              :label=\"$t('preferences.max-concurrent-downloads')\">\n            </el-input-number>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            {{ $t('preferences.max-connection-per-server') }}\n            <el-input-number\n              v-model=\"form.maxConnectionPerServer\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"form.engineMaxConnectionPerServer\"\n              :label=\"$t('preferences.max-connection-per-server')\">\n            </el-input-number>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.continue\">\n              {{ $t('preferences.continue') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.newTaskShowDownloading\">\n              {{ $t('preferences.new-task-show-downloading') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.taskNotification\">\n              {{ $t('preferences.task-completed-notify') }}\n            </el-checkbox>\n          </el-col>\n          <el-col class=\"form-item-sub\" :span=\"24\">\n            <el-checkbox v-model=\"form.noConfirmBeforeDeleteTask\">\n              {{ $t('preferences.no-confirm-before-delete-task') }}\n            </el-checkbox>\n          </el-col>\n        </el-form-item>\n      </el-form>\n      <div class=\"form-actions\">\n        <el-button\n          type=\"primary\"\n          @click=\"submitForm('basicForm')\"\n        >\n          {{ $t('preferences.save') }}\n        </el-button>\n        <el-button\n          @click=\"resetForm('basicForm')\"\n        >\n          {{ $t('preferences.discard') }}\n        </el-button>\n      </div>\n    </el-main>\n  </el-container>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { dialog } from '@electron/remote'\n  import { mapState } from 'vuex'\n  import { cloneDeep, extend, isEmpty } from 'lodash'\n  import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'\n  import HistoryDirectory from '@/components/Preference/HistoryDirectory'\n  import SelectDirectory from '@/components/Native/SelectDirectory'\n  import ThemeSwitcher from '@/components/Preference/ThemeSwitcher'\n  import { availableLanguages, getLanguage } from '@shared/locales'\n  import { getLocaleManager } from '@/components/Locale'\n  import {\n    calcFormLabelWidth,\n    changedConfig,\n    checkIsNeedRestart,\n    convertLineToComma,\n    diffConfig,\n    extractSpeedUnit\n  } from '@shared/utils'\n  import {\n    APP_RUN_MODE,\n    EMPTY_STRING,\n    ENGINE_MAX_CONCURRENT_DOWNLOADS,\n    ENGINE_RPC_PORT\n  } from '@shared/constants'\n  import { reduceTrackerString } from '@shared/utils/tracker'\n\n  const initForm = (config) => {\n    const {\n      autoHideWindow,\n      btForceEncryption,\n      btSaveMetadata,\n      dir,\n      engineMaxConnectionPerServer,\n      followMetalink,\n      followTorrent,\n      hideAppMenu,\n      keepSeeding,\n      keepWindowState,\n      locale,\n      maxConcurrentDownloads,\n      maxConnectionPerServer,\n      maxOverallDownloadLimit,\n      maxOverallUploadLimit,\n      newTaskShowDownloading,\n      noConfirmBeforeDeleteTask,\n      openAtLogin,\n      pauseMetadata,\n      resumeAllWhenAppLaunched,\n      runMode,\n      seedRatio,\n      seedTime,\n      showProgressBar,\n      taskNotification,\n      theme,\n      traySpeedometer\n    } = config\n\n    const btAutoDownloadContent = followTorrent &&\n      followMetalink &&\n      !pauseMetadata\n\n    const result = {\n      autoHideWindow,\n      btAutoDownloadContent,\n      btForceEncryption,\n      btSaveMetadata,\n      continue: config.continue,\n      dir,\n      engineMaxConnectionPerServer,\n      followMetalink,\n      followTorrent,\n      hideAppMenu,\n      keepSeeding,\n      keepWindowState,\n      locale,\n      maxConcurrentDownloads,\n      maxConnectionPerServer,\n      maxOverallDownloadLimit,\n      maxOverallUploadLimit,\n      newTaskShowDownloading,\n      noConfirmBeforeDeleteTask,\n      openAtLogin,\n      pauseMetadata,\n      resumeAllWhenAppLaunched,\n      runMode,\n      seedRatio,\n      seedTime,\n      showProgressBar,\n      taskNotification,\n      theme,\n      traySpeedometer\n    }\n    return result\n  }\n\n  export default {\n    name: 'mo-preference-basic',\n    components: {\n      [SubnavSwitcher.name]: SubnavSwitcher,\n      [HistoryDirectory.name]: HistoryDirectory,\n      [SelectDirectory.name]: SelectDirectory,\n      [ThemeSwitcher.name]: ThemeSwitcher\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      const formOriginal = initForm(this.$store.state.preference.config)\n      let form = {}\n      form = initForm(extend(form, formOriginal, changedConfig.basic))\n\n      return {\n        form,\n        formLabelWidth: calcFormLabelWidth(locale),\n        formOriginal,\n        locales: availableLanguages,\n        rules: {}\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      isMac: () => is.macOS(),\n      isMas: () => is.mas(),\n      isLinux () { return is.linux() },\n      title () {\n        return this.$t('preferences.basic')\n      },\n      maxConcurrentDownloads () {\n        return ENGINE_MAX_CONCURRENT_DOWNLOADS\n      },\n      maxOverallDownloadLimitParsed: {\n        get () {\n          return parseInt(this.form.maxOverallDownloadLimit)\n        },\n        set (value) {\n          const limit = value > 0 ? `${value}${this.downloadUnit}` : 0\n          this.form.maxOverallDownloadLimit = limit\n        }\n      },\n      maxOverallUploadLimitParsed: {\n        get () {\n          return parseInt(this.form.maxOverallUploadLimit)\n        },\n        set (value) {\n          const limit = value > 0 ? `${value}${this.uploadUnit}` : 0\n          this.form.maxOverallUploadLimit = limit\n        }\n      },\n      downloadUnit: {\n        get () {\n          const { maxOverallDownloadLimit } = this.form\n          return extractSpeedUnit(maxOverallDownloadLimit)\n        },\n        set (value) {\n          return value\n        }\n      },\n      uploadUnit: {\n        get () {\n          const { maxOverallUploadLimit } = this.form\n          return extractSpeedUnit(maxOverallUploadLimit)\n        },\n        set (value) {\n          return value\n        }\n      },\n      runModes () {\n        let result = [\n          {\n            label: this.$t('preferences.run-mode-standard'),\n            value: APP_RUN_MODE.STANDARD\n          },\n          {\n            label: this.$t('preferences.run-mode-tray'),\n            value: APP_RUN_MODE.TRAY\n          }\n        ]\n\n        if (this.isMac) {\n          result = [\n            ...result,\n            {\n              label: this.$t('preferences.run-mode-hide-tray'),\n              value: APP_RUN_MODE.HIDE_TRAY\n            }\n          ]\n        }\n\n        return result\n      },\n      speedUnits () {\n        return [\n          {\n            label: 'KB/s',\n            value: 'K'\n          },\n          {\n            label: 'MB/s',\n            value: 'M'\n          }\n        ]\n      },\n      subnavs () {\n        return [\n          {\n            key: 'basic',\n            title: this.$t('preferences.basic'),\n            route: '/preference/basic'\n          },\n          {\n            key: 'advanced',\n            title: this.$t('preferences.advanced'),\n            route: '/preference/advanced'\n          },\n          {\n            key: 'lab',\n            title: this.$t('preferences.lab'),\n            route: '/preference/lab'\n          }\n        ]\n      },\n      showHideAppMenuOption () {\n        return is.windows() || is.linux()\n      },\n      rpcDefaultPort () {\n        return ENGINE_RPC_PORT\n      },\n      ...mapState('preference', {\n        config: state => state.config\n      })\n    },\n    methods: {\n      handleLocaleChange (locale) {\n        const lng = getLanguage(locale)\n        getLocaleManager().changeLanguage(lng)\n      },\n      handleThemeChange (theme) {\n        this.form.theme = theme\n      },\n      handleDownloadChange (value) {\n        const speedLimit = parseInt(this.form.maxOverallDownloadLimit, 10)\n        this.downloadUnit = value\n        const limit = speedLimit > 0 ? `${speedLimit}${value}` : 0\n        this.form.maxOverallDownloadLimit = limit\n      },\n      handleUploadChange (value) {\n        const speedLimit = parseInt(this.form.maxOverallUploadLimit, 10)\n        this.uploadUnit = value\n        const limit = speedLimit > 0 ? `${speedLimit}${value}` : 0\n        this.form.maxOverallUploadLimit = limit\n      },\n      onKeepSeedingChange (enable) {\n        this.form.seedRatio = enable ? 0 : 1\n        this.form.seedTime = enable ? 525600 : 60\n      },\n      handleHistoryDirectorySelected (dir) {\n        this.form.dir = dir\n      },\n      handleNativeDirectorySelected (dir) {\n        this.form.dir = dir\n        this.$store.dispatch('preference/recordHistoryDirectory', dir)\n      },\n      onDirectorySelected (dir) {\n        this.form.dir = dir\n      },\n      syncFormConfig () {\n        this.$store.dispatch('preference/fetchPreference')\n          .then((config) => {\n            this.form = initForm(config)\n            this.formOriginal = cloneDeep(this.form)\n          })\n      },\n      submitForm (formName) {\n        this.$refs[formName].validate((valid) => {\n          if (!valid) {\n            console.error('[Motrix] preference form valid:', valid)\n            return false\n          }\n\n          const data = {\n            ...diffConfig(this.formOriginal, this.form),\n            ...changedConfig.advanced\n          }\n\n          const {\n            autoHideWindow,\n            btAutoDownloadContent,\n            btTracker,\n            rpcListenPort\n          } = data\n\n          if ('btAutoDownloadContent' in data) {\n            data.followTorrent = btAutoDownloadContent\n            data.followMetalink = btAutoDownloadContent\n            data.pauseMetadata = !btAutoDownloadContent\n          }\n\n          if (btTracker) {\n            data.btTracker = reduceTrackerString(convertLineToComma(btTracker))\n          }\n\n          if (rpcListenPort === EMPTY_STRING) {\n            data.rpcListenPort = this.rpcDefaultPort\n          }\n\n          console.log('[Motrix] preference changed data:', data)\n\n          this.$store.dispatch('preference/save', data)\n            .then(() => {\n              this.$store.dispatch('app/fetchEngineOptions')\n              this.syncFormConfig()\n              this.$msg.success(this.$t('preferences.save-success-message'))\n            })\n            .catch(() => {\n              this.$msg.success(this.$t('preferences.save-fail-message'))\n            })\n\n          changedConfig.basic = {}\n          changedConfig.advanced = {}\n\n          if (this.isRenderer) {\n            if ('autoHideWindow' in data) {\n              this.$electron.ipcRenderer.send('command',\n                                              'application:auto-hide-window', autoHideWindow)\n            }\n\n            if (checkIsNeedRestart(data)) {\n              this.$electron.ipcRenderer.send('command', 'application:relaunch')\n            }\n          }\n        })\n      },\n      resetForm (formName) {\n        this.syncFormConfig()\n      }\n    },\n    beforeRouteLeave (to, from, next) {\n      changedConfig.basic = diffConfig(this.formOriginal, this.form)\n      if (to.path === '/preference/advanced') {\n        next()\n      } else {\n        if (isEmpty(changedConfig.basic) && isEmpty(changedConfig.advanced)) {\n          next()\n        } else {\n          dialog.showMessageBox({\n            type: 'warning',\n            title: this.$t('preferences.not-saved'),\n            message: this.$t('preferences.not-saved-confirm'),\n            buttons: [this.$t('app.yes'), this.$t('app.no')],\n            cancelId: 1\n          }).then(({ response }) => {\n            if (response === 0) {\n              changedConfig.basic = {}\n              changedConfig.advanced = {}\n              next()\n            }\n          })\n        }\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Preference/HistoryDirectory.vue",
    "content": "<template>\n  <div class=\"mo-history-directory\">\n    <el-popover\n      popper-class=\"mo-directory-popper\"\n      trigger=\"hover\"\n      :placement=\"placement\"\n      :width=\"width\"\n    >\n      <el-empty class=\"mo-directory-empty\" :image-size=\"48\" v-if=\"empty\" />\n      <ul class=\"mo-directory-list\" v-if=\"favoriteDirectories.length > 0\">\n        <li\n          v-for=\"directory in favoriteDirectories\"\n          :key=\"directory\"\n          @click.stop=\"() => handleSelectItem(directory)\"\n        >\n          <span class=\"mo-directory-path\" :title=\"directory\">{{directory}}</span>\n          <span class=\"mo-directory-actions\">\n            <i\n              class=\"el-icon-star-off icon-history-favorited\"\n              @click.stop=\"() => handleCancelFavoriteItem(directory)\"\n            />\n            <i\n              class=\"el-icon-delete icon-history-remove\"\n              @click.stop=\"() => handleRemoveItem(directory)\"\n            />\n          </span>\n        </li>\n      </ul>\n      <div class=\"mo-directory-divider\" v-if=\"showDivider\" />\n      <ul class=\"mo-directory-list\" v-if=\"historyDirectories.length > 0\">\n        <li\n          v-for=\"directory in historyDirectories\"\n          :key=\"directory\"\n          @click.stop=\"() => handleSelectItem(directory)\"\n        >\n          <span class=\"mo-directory-path\" :title=\"directory\">{{directory}}</span>\n          <span class=\"mo-directory-actions\">\n            <i\n              v-if=\"showFavoriteAction\"\n              class=\"el-icon-star-off icon-history-favorite\"\n              @click.stop=\"() => handleFavoriteItem(directory)\"\n            />\n            <i\n              class=\"el-icon-delete icon-history-remove\"\n              @click.stop=\"() => handleRemoveItem(directory)\"\n            />\n          </span>\n        </li>\n      </ul>\n      <el-button\n        slot=\"reference\"\n        :disabled=\"popoverDisabled\"\n      >\n        <i class=\"el-icon-time\" />\n      </el-button>\n    </el-popover>\n  </div>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import { MAX_NUM_OF_DIRECTORIES } from '@shared/constants'\n  import { cloneArray } from '@shared/utils'\n\n  export default {\n    name: 'mo-history-directory',\n    components: {\n    },\n    props: {\n      width: {\n        type: Number,\n        default: 360\n      },\n      placement: {\n        type: String,\n        default: 'bottom-start'\n      }\n    },\n    data () {\n      return {\n        visible: false\n      }\n    },\n    computed: {\n      ...mapState('preference', {\n        historyDirectories: state => {\n          return cloneArray(state.config.historyDirectories, true)\n        },\n        favoriteDirectories: state => {\n          return cloneArray(state.config.favoriteDirectories, true)\n        }\n      }),\n      empty () {\n        const { favoriteDirectories, historyDirectories } = this\n        return favoriteDirectories.length + historyDirectories.length === 0\n      },\n      popoverDisabled () {\n        const { favoriteDirectories, historyDirectories } = this\n        return favoriteDirectories.length === 0 &&\n          historyDirectories.length === 0\n      },\n      showDivider () {\n        const { favoriteDirectories, historyDirectories } = this\n        return favoriteDirectories.length > 0 &&\n          historyDirectories.length > 0\n      },\n      showFavoriteAction () {\n        const { favoriteDirectories } = this\n        return favoriteDirectories.length < MAX_NUM_OF_DIRECTORIES\n      }\n    },\n    methods: {\n      handleIconClick () {\n        if (this.popoverDisabled) {\n          return\n        }\n\n        const { visible } = this\n        this.visible = !visible\n      },\n      handleSelectItem (directory) {\n        this.$emit('selected', directory.trim())\n        this.visible = false\n      },\n      handleFavoriteItem (directory) {\n        console.log('handleFavoriteItem==>', directory)\n        this.$store.dispatch('preference/favoriteDirectory', directory)\n      },\n      handleCancelFavoriteItem (directory) {\n        console.log('handleCancelFavoriteItem==>', directory)\n        this.$store.dispatch('preference/cancelFavoriteDirectory', directory)\n      },\n      handleRemoveItem (directory) {\n        console.log('handleRemoveItem==>', directory)\n        this.$store.dispatch('preference/removeDirectory', directory)\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.el-popover.mo-directory-popper {\n  padding: $--popover-padding 0;\n}\n\n.el-empty.mo-directory-empty {\n  padding: 20px 0;\n}\n\n.mo-directory-divider {\n  padding: 0 $--popover-padding;\n  margin: 6px 0;\n  &::after {\n    content: ' ';\n    display: block;\n    height: 1px;\n    width: 100%;\n    background: $--border-color-base;\n  }\n}\n\n.mo-directory-list {\n  padding: 0;\n  margin: 0;\n  list-style: none;\n  &> li {\n    display: flex;\n    align-items: center;\n    list-style: none;\n    line-height: $--font-line-height-primary;\n    margin: 0;\n    font-size: $--font-size-small;\n    color: $--color-text-regular;\n    cursor: pointer;\n    outline: none;\n    padding: 6px 6px 6px $--popover-padding;\n    &:focus, &:hover {\n      background-color: $--background-color-base;\n      color: $--color-primary-light-2;\n    }\n  }\n  .mo-directory-path {\n    display: inline-block;\n    flex: 1;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n  .mo-directory-actions {\n    min-width: 40px;\n    text-align: right;\n    &> i {\n      padding: 3px;\n      margin-right: 3px;\n      display: inline-block;\n    }\n  }\n  .icon-history-favorite {\n    &:focus, &:hover {\n      color: $--color-warning;\n    }\n  }\n  .icon-history-favorited {\n    color: $--color-warning;\n  }\n  .icon-history-remove {\n    &:focus, &:hover {\n      color: $--color-danger;\n    }\n  }\n}\n\n.theme-dark {\n  .mo-directory-divider {\n    &::after {\n      background: $--dk-border-color-base;\n    }\n  }\n  .mo-directory-list {\n    &> li {\n      color: $--dk-font-color-base;\n      &:focus, &:hover {\n        background-color: $--color-primary;\n        color: $--color-white;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Preference/Index.vue",
    "content": "<template>\n  <el-container class=\"main panel\" direction=\"horizontal\">\n    <el-aside width=\"200px\" class=\"subnav hidden-xs-only\">\n      <router-view name=\"subnav\" />\n    </el-aside>\n    <router-view name=\"form\" />\n  </el-container>\n</template>\n\n<script>\n  export default {\n    name: 'mo-content-preference',\n    created () {\n      this.$store.dispatch('preference/fetchPreference')\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.form-preference {\n  padding: 16px 7% 64px 16px;\n  .el-switch__label {\n    font-weight: normal;\n    color: $--color-text-regular;\n    &.is-active {\n      color: $--color-text-regular;\n    }\n  }\n  .el-checkbox__input.is-checked + .el-checkbox__label {\n    color: $--color-text-regular;\n  }\n  .el-form-item {\n    a {\n      color: $--color-text-regular;\n      text-decoration: none;\n      &:hover {\n        color: $--color-text-primary;\n        text-decoration: underline;\n      }\n      &:active {\n        color: $--color-text-primary;\n      }\n    }\n  }\n  .el-form-item.el-form-item--mini {\n    margin-bottom: 32px;\n  }\n  .el-form-item__content {\n    color: $--color-text-regular;\n  }\n  .form-item-sub {\n    margin-bottom: 8px;\n    &:last-of-type {\n      margin-bottom: 0;\n    }\n  }\n}\n.form-actions {\n  position: sticky;\n  bottom: 0;\n  left: auto;\n  z-index: 10;\n  width: -webkit-fill-available;\n  box-sizing: border-box;\n  padding: 24px 16px;\n}\n.action-link {\n  cursor: pointer;\n  color: $--link-color;\n  &:hover {\n    color: $--link-hover-color;\n    text-decoration: underline;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Preference/Lab.vue",
    "content": "<template>\n  <el-container class=\"content panel\" direction=\"vertical\">\n    <el-header class=\"panel-header\" height=\"84\">\n      <h4 class=\"hidden-xs-only\">{{ title }}</h4>\n      <mo-subnav-switcher\n        :title=\"title\"\n        :subnavs=\"subnavs\"\n        class=\"hidden-sm-and-up\"\n      />\n    </el-header>\n    <mo-browser\n      v-if=\"isRenderer\"\n      class=\"lab-webview\"\n      :src=\"url\"\n    />\n  </el-container>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapState } from 'vuex'\n\n  import { APP_THEME } from '@shared/constants'\n  import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'\n  import Browser from '@/components/Browser'\n  import '@/components/Icons/info-square'\n\n  export default {\n    name: 'mo-preference-lab',\n    components: {\n      [SubnavSwitcher.name]: SubnavSwitcher,\n      [Browser.name]: Browser\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      return {\n        locale\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      ...mapState('app', {\n        systemTheme: state => state.systemTheme\n      }),\n      ...mapState('preference', {\n        config: state => state.config,\n        theme: state => state.config.theme\n      }),\n      currentTheme () {\n        if (this.theme === APP_THEME.AUTO) {\n          return this.systemTheme\n        } else {\n          return this.theme\n        }\n      },\n      url () {\n        const { currentTheme, locale } = this\n        const result = `https://motrix.app/lab?lite=true&theme=${currentTheme}&lang=${locale}`\n        return result\n      },\n      title () {\n        return this.$t('preferences.lab')\n      },\n      subnavs () {\n        return [\n          {\n            key: 'basic',\n            title: this.$t('preferences.basic'),\n            route: '/preference/basic'\n          },\n          {\n            key: 'advanced',\n            title: this.$t('preferences.advanced'),\n            route: '/preference/advanced'\n          },\n          {\n            key: 'lab',\n            title: this.$t('preferences.lab'),\n            route: '/preference/lab'\n          }\n        ]\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.lab-webview {\n  display: inline-flex;;\n  flex: 1;\n  flex-basis: auto;\n  overflow: auto;\n  box-sizing: border-box;\n  padding: 0;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Preference/ThemeSwitcher.vue",
    "content": "<template>\n  <div>\n    <ul class=\"theme-switcher\">\n      <li\n        v-for=\"item in themeOptions\"\n        :class=\"['theme-item', item.className, { active: currentValue === item.value }]\"\n        :key=\"item.value\"\n        @click.prevent=\"() => handleChange(item.value)\"\n      >\n        <div class=\"theme-thumb\"></div>\n        <span>{{ item.text }}</span>\n      </li>\n    </ul>\n  </div>\n</template>\n\n<script>\n  import { APP_THEME } from '@shared/constants'\n\n  export default {\n    name: 'mo-theme-switcher',\n    props: {\n      value: {\n        type: String,\n        default: APP_THEME.AUTO\n      }\n    },\n    data () {\n      return {\n        currentValue: this.value\n      }\n    },\n    computed: {\n      themeOptions () {\n        return [\n          {\n            className: 'theme-item-auto',\n            value: APP_THEME.AUTO,\n            text: this.$t('preferences.theme-auto')\n          },\n          {\n            className: 'theme-item-light',\n            value: APP_THEME.LIGHT,\n            text: this.$t('preferences.theme-light')\n          },\n          {\n            className: 'theme-item-dark',\n            value: APP_THEME.DARK,\n            text: this.$t('preferences.theme-dark')\n          }\n        ]\n      }\n    },\n    watch: {\n      currentValue (val) {\n        this.$emit('change', val)\n      }\n    },\n    methods: {\n      handleChange (theme) {\n        this.currentValue = theme\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.theme-switcher {\n  padding: 0;\n  margin: 0;\n  font-size: 0;\n  line-height: 0;\n  .theme-item {\n    text-align: center;\n    display: inline-block;\n    margin: 0 16px 0 0;\n    cursor: pointer;\n    span {\n      font-size: 13px;\n      line-height: 20px;\n    }\n    &.active {\n      .theme-thumb {\n        border-color: $--color-primary;\n        box-shadow: 0 0 1px $--color-primary;\n      }\n      span {\n        color: $--color-primary;\n      }\n    }\n    &.theme-item-auto .theme-thumb {\n      background: url('~@/assets/theme-auto@2x.png') center center no-repeat;\n      background-size: 68px 44px;\n    }\n    &.theme-item-light .theme-thumb {\n      background: url('~@/assets/theme-light@2x.png') center center no-repeat;\n      background-size: 68px 44px;\n    }\n    &.theme-item-dark .theme-thumb {\n      background: url('~@/assets/theme-dark@2x.png') center center no-repeat;\n      background-size: 68px 44px;\n    }\n  }\n  .theme-thumb {\n    box-sizing: border-box;\n    border: 1px solid #aaa;\n    border-radius: 5px;\n    width: 68px;\n    height: 44px;\n    margin-bottom: 8px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Speedometer/Speedometer.vue",
    "content": "<template>\n  <div class=\"mo-speedometer\" :class=\"{ stopped: stat.numActive === 0 }\">\n    <div\n      class=\"mode\"\n      @click=\"toggleEngineMode\"\n    >\n      <i>\n        <mo-icon name=\"speedometer\" width=\"24\" height=\"24\" />\n      </i>\n      <em>{{ engineMode }}</em>\n    </div>\n    <div class=\"value\" v-if=\"stat.numActive > 0\">\n      <em>{{ stat.uploadSpeed | bytesToSize }}/s</em>\n      <span>{{ stat.downloadSpeed | bytesToSize }}/s</span>\n    </div>\n  </div>\n</template>\n\n<script>\n  import { mapState, mapActions } from 'vuex'\n  import { bytesToSize } from '@shared/utils'\n  import '@/components/Icons/speedometer'\n\n  export default {\n    name: 'mo-speedometer',\n    computed: {\n      ...mapState('app', [\n        'stat'\n      ]),\n      ...mapState('preference', [\n        'engineMode'\n      ])\n    },\n    filters: {\n      bytesToSize\n    },\n    methods: {\n      ...mapActions('preference', [\n        'toggleEngineMode'\n      ])\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n  .mo-speedometer {\n    font-size: 12px;\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    width: 150px;\n    height: 40px;\n    padding: 5px 10px 5px 48px;\n    border-radius: 100px;\n    transition: $--all-transition;\n    border: 1px solid $--speedometer-border-color;\n    background: $--speedometer-background;\n    &:hover {\n      border-color: $--speedometer-hover-border-color;\n    }\n    &.stopped {\n      width: 40px;\n      padding: 0;\n      .mode i {\n        color: $--speedometer-stopped-color;\n      }\n      .mode em {\n        display: none;\n      }\n\n    }\n    em {\n      font-style: normal;\n    }\n    .mode {\n      font-size: 0;\n      position: absolute;\n      top: 5px;\n      left: 5px;\n    }\n    .mode i {\n      font-size: 20px;\n      font-style: normal;\n      line-height: 28px;\n      display: inline-block;\n      box-sizing: border-box;\n      width: 28px;\n      height: 28px;\n      padding: 2px;\n      text-align: center;\n      vertical-align: top;\n      color: $--speedometer-primary-color;\n    }\n    .mode em {\n      display: inline-block;\n      width: 0;\n      height: 8px;\n      margin-left: 4px;\n      font-size: 16px;\n      line-height: 15px;\n      transform: scale(.5);\n      vertical-align: top;\n      color: $--speedometer-primary-color;\n    }\n    .mode.mode-auto em {\n      color: $--speedometer-text-color;\n    }\n    .mode.mode-max em {\n      color: $--speedometer-primary-color;\n    }\n    .value {\n      font-size: 0;\n      overflow: hidden;\n      width: 100%;\n      text-align: right;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n    .value em {\n      font-size: 16px;\n      line-height: 12px;\n      display: block;\n      width: 120%;\n      transform: scale(.625);\n      color: $--speedometer-text-color;\n    }\n    .value span {\n      font-size: 13px;\n      line-height: 14px;\n      display: block;\n      margin-top: 2px;\n      color: $--speedometer-primary-color;\n    }\n    .no-active {\n      font-size: 14px;\n      line-height: 28px;\n      color: $--speedometer-primary-color;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/renderer/components/Subnav/PreferenceSubnav.vue",
    "content": "<template>\n  <nav class=\"subnav-inner\">\n    <h3>{{ title }}</h3>\n    <ul>\n      <li\n        @click=\"() => nav('basic')\"\n        :class=\"[ current === 'basic' ? 'active' : '' ]\"\n        >\n        <i class=\"subnav-icon\">\n          <mo-icon name='preference-basic' width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('preferences.basic') }}</span>\n      </li>\n      <li\n        @click=\"() => nav('advanced')\"\n        :class=\"[ current === 'advanced' ? 'active' : '' ]\"\n        >\n        <i class=\"subnav-icon\">\n          <mo-icon name='preference-advanced' width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('preferences.advanced') }}</span>\n      </li>\n      <li\n        @click=\"() => nav('lab')\"\n        :class=\"[ current === 'lab' ? 'active' : '' ]\"\n        >\n        <i class=\"subnav-icon\">\n          <mo-icon name='preference-lab' width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('preferences.lab') }}</span>\n      </li>\n    </ul>\n  </nav>\n</template>\n\n<script>\n  import '@/components/Icons/preference-basic'\n  import '@/components/Icons/preference-advanced'\n  import '@/components/Icons/preference-lab'\n\n  export default {\n    name: 'mo-preference-subnav',\n    props: {\n      current: {\n        type: String,\n        default: 'basic'\n      }\n    },\n    computed: {\n      title () {\n        return this.$t('subnav.preferences')\n      }\n    },\n    methods: {\n      nav (category = 'basic') {\n        this.$router.push({\n          path: `/preference/${category}`\n        }).catch(err => {\n          console.log(err)\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Subnav/SubnavSwitcher.vue",
    "content": "<template>\n  <el-dropdown @command=\"handleRoute\" class=\"subnav-switch\" size=\"medium\">\n    <h4 class=\"subnav-title\">\n      {{ title }}\n      <i class=\"el-icon-arrow-down el-icon--right\" />\n    </h4>\n    <el-dropdown-menu slot=\"dropdown\" class=\"subnav-switch-dropdown\">\n      <el-dropdown-item :command=\"sn.route\" v-for=\"sn in subnavs\" :key=\"sn.key\">\n        {{ sn.title }}\n      </el-dropdown-item>\n    </el-dropdown-menu>\n  </el-dropdown>\n</template>\n\n<script>\n  export default {\n    name: 'mo-subnav-switcher',\n    props: {\n      title: {\n        type: String\n      },\n      subnavs: {\n        type: Array\n      }\n    },\n    methods: {\n      handleRoute (route) {\n        this.$router.push({\n          path: route\n        }).catch(err => {\n          console.log(err)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang='scss'>\n.subnav-switch-dropdown {\n  background: $--select-dropdown-background;\n  & .el-dropdown-menu__item {\n    font-size: 16px;\n    color: $--select-option-color;\n  }\n}\n.subnav-switch {\n  & .subnav-title {\n    color: $--subnav-action-color;\n    font-size: 16px;\n  }\n}\n.theme-dark {\n  .subnav-switch-dropdown {\n    background-color: $--dk-subnav-background;\n    border-color: $--dk-subnav-border-color;\n    color: $--dk-subnav-text-color;\n    & .el-dropdown-menu__item {\n      color: $--dk-subnav-text-color;\n      &.selected {\n        color: $--color-primary;\n      }\n      &.hover,\n      &:hover {\n        background-color: $--color-primary;\n        color: $--dk-titlebar-close-active-color;\n      }\n    }\n  }\n  & .el-dropdown {\n    & .subnav-title {\n      color: $--dk-subnav-action-color;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Subnav/TaskSubnav.vue",
    "content": "<template>\n  <nav class=\"subnav-inner\">\n    <h3>{{ title }}</h3>\n    <ul>\n      <li\n        @click=\"() => nav('active')\"\n        :class=\"[ current === 'active' ? 'active' : '' ]\"\n      >\n        <i class=\"subnav-icon\">\n          <mo-icon name=\"task-start\" width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('task.active') }}</span>\n      </li>\n      <li\n        @click=\"() => nav('waiting')\"\n        :class=\"[ current === 'waiting' ? 'active' : '' ]\"\n      >\n        <i class=\"subnav-icon\">\n          <mo-icon name=\"task-pause\" width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('task.waiting') }}</span>\n      </li>\n      <li\n        @click=\"() => nav('stopped')\"\n        :class=\"[ current === 'stopped' ? 'active' : '' ]\"\n      >\n        <i class=\"subnav-icon\">\n          <mo-icon name=\"task-stop\" width=\"20\" height=\"20\" />\n        </i>\n        <span>{{ $t('task.stopped') }}</span>\n      </li>\n    </ul>\n  </nav>\n</template>\n\n<script>\n  import '@/components/Icons/task-start'\n  import '@/components/Icons/task-pause'\n  import '@/components/Icons/task-stop'\n\n  export default {\n    name: 'mo-task-subnav',\n    props: {\n      current: {\n        type: String,\n        default: 'active'\n      }\n    },\n    computed: {\n      title () {\n        return this.$t('subnav.task-list')\n      }\n    },\n    methods: {\n      nav (status = 'active') {\n        this.$router.push({\n          path: `/task/${status}`\n        }).catch(err => {\n          console.log(err)\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Task/AddTask.vue",
    "content": "<template>\n  <el-dialog\n    custom-class=\"tab-title-dialog add-task-dialog\"\n    width=\"67vw\"\n    :visible=\"visible\"\n    :top=\"dialogTop\"\n    :show-close=\"false\"\n    :before-close=\"beforeClose\"\n    @open=\"handleOpen\"\n    @opened=\"handleOpened\"\n    @closed=\"handleClosed\"\n  >\n    <el-form ref=\"taskForm\" label-position=\"left\" :model=\"form\" :rules=\"rules\">\n      <el-tabs :value=\"type\" @tab-click=\"handleTabClick\">\n        <el-tab-pane :label=\"$t('task.uri-task')\" name=\"uri\">\n          <el-form-item>\n            <el-input\n              ref=\"uri\"\n              type=\"textarea\"\n              auto-complete=\"off\"\n              :autosize=\"{ minRows: 3, maxRows: 5 }\"\n              :placeholder=\"$t('task.uri-task-tips')\"\n              @paste.native=\"handleUriPaste\"\n              v-model=\"form.uris\"\n            >\n            </el-input>\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane :label=\"$t('task.torrent-task')\" name=\"torrent\">\n          <el-form-item>\n            <mo-select-torrent v-on:change=\"handleTorrentChange\" />\n          </el-form-item>\n        </el-tab-pane>\n      </el-tabs>\n      <el-row :gutter=\"12\">\n        <el-col :span=\"15\" :xs=\"24\">\n          <el-form-item\n            :label=\"`${$t('task.task-out')}: `\"\n            :label-width=\"formLabelWidth\"\n          >\n            <el-input\n              :placeholder=\"$t('task.task-out-tips')\"\n              v-model=\"form.out\"\n            >\n            </el-input>\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"9\" :xs=\"24\">\n          <el-form-item\n            :label=\"`${$t('task.task-split')}: `\"\n            :label-width=\"formLabelWidth\"\n          >\n            <el-input-number\n              v-model=\"form.split\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"config.engineMaxConnectionPerServer\"\n              :label=\"$t('task.task-split')\"\n            >\n            </el-input-number>\n          </el-form-item>\n        </el-col>\n      </el-row>\n      <el-form-item\n        :label=\"`${$t('task.task-dir')}: `\"\n        :label-width=\"formLabelWidth\"\n      >\n        <el-input\n          placeholder=\"\"\n          v-model=\"form.dir\"\n          :readonly=\"isMas\"\n        >\n          <mo-history-directory\n            slot=\"prepend\"\n            @selected=\"handleHistoryDirectorySelected\"\n          />\n          <mo-select-directory\n            v-if=\"isRenderer\"\n            slot=\"append\"\n            @selected=\"handleNativeDirectorySelected\"\n          />\n        </el-input>\n      </el-form-item>\n      <div class=\"task-advanced-options\" v-if=\"showAdvanced\">\n        <el-form-item\n          :label=\"`${$t('task.task-user-agent')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-input\n            type=\"textarea\"\n            auto-complete=\"off\"\n            :autosize=\"{ minRows: 2, maxRows: 3 }\"\n            :placeholder=\"$t('task.task-user-agent')\"\n            v-model=\"form.userAgent\"\n          >\n          </el-input>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('task.task-authorization')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-input\n            type=\"textarea\"\n            auto-complete=\"off\"\n            :autosize=\"{ minRows: 2, maxRows: 3 }\"\n            :placeholder=\"$t('task.task-authorization')\"\n            v-model=\"form.authorization\"\n          >\n          </el-input>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('task.task-referer')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-input\n            type=\"textarea\"\n            auto-complete=\"off\"\n            :autosize=\"{ minRows: 2, maxRows: 3 }\"\n            :placeholder=\"$t('task.task-referer')\"\n            v-model=\"form.referer\"\n          >\n          </el-input>\n        </el-form-item>\n        <el-form-item\n          :label=\"`${$t('task.task-cookie')}: `\"\n          :label-width=\"formLabelWidth\"\n        >\n          <el-input\n            type=\"textarea\"\n            auto-complete=\"off\"\n            :autosize=\"{ minRows: 2, maxRows: 3 }\"\n            :placeholder=\"$t('task.task-cookie')\"\n            v-model=\"form.cookie\"\n          >\n          </el-input>\n        </el-form-item>\n        <el-row :gutter=\"12\">\n          <el-col :span=\"16\" :xs=\"24\">\n            <el-form-item\n              :label=\"`${$t('task.task-proxy')}: `\"\n              :label-width=\"formLabelWidth\"\n            >\n              <el-input\n                placeholder=\"[http://][USER:PASSWORD@]HOST[:PORT]\"\n                v-model=\"form.allProxy\">\n              </el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\" :xs=\"24\">\n            <div class=\"help-link\">\n              <a target=\"_blank\" href=\"https://github.com/agalwood/Motrix/wiki/Proxy\" rel=\"noopener noreferrer\">\n                {{ $t('preferences.proxy-tips') }}\n                <mo-icon name=\"link\" width=\"12\" height=\"12\" />\n              </a>\n            </div>\n          </el-col>\n        </el-row>\n        <el-form-item label=\"\" :label-width=\"formLabelWidth\" style=\"margin-top: 12px;\">\n          <el-checkbox class=\"chk\" v-model=\"form.newTaskShowDownloading\">\n            {{$t('task.navigate-to-downloading')}}\n          </el-checkbox>\n        </el-form-item>\n      </div>\n    </el-form>\n    <button\n      slot=\"title\"\n      type=\"button\"\n      class=\"el-dialog__headerbtn\"\n      aria-label=\"Close\"\n      @click=\"handleClose\">\n      <i class=\"el-dialog__close el-icon el-icon-close\"></i>\n    </button>\n    <div slot=\"footer\" class=\"dialog-footer\">\n      <el-row>\n        <el-col :span=\"9\" :xs=\"9\">\n          <el-checkbox class=\"chk\" v-model=\"showAdvanced\">\n            {{$t('task.show-advanced-options')}}\n          </el-checkbox>\n        </el-col>\n        <el-col :span=\"15\" :xs=\"15\">\n          <el-button @click=\"handleCancel('taskForm')\">\n            {{$t('app.cancel')}}\n          </el-button>\n          <el-button\n            type=\"primary\"\n            @click=\"submitForm('taskForm')\"\n          >\n            {{$t('app.submit')}}\n          </el-button>\n        </el-col>\n      </el-row>\n    </div>\n  </el-dialog>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapState } from 'vuex'\n  import { isEmpty } from 'lodash'\n  import HistoryDirectory from '@/components/Preference/HistoryDirectory'\n  import SelectDirectory from '@/components/Native/SelectDirectory'\n  import SelectTorrent from '@/components/Task/SelectTorrent'\n  import {\n    initTaskForm,\n    buildUriPayload,\n    buildTorrentPayload\n  } from '@/utils/task'\n  import { ADD_TASK_TYPE } from '@shared/constants'\n  import { detectResource } from '@shared/utils'\n  import '@/components/Icons/inbox'\n\n  export default {\n    name: 'mo-add-task',\n    components: {\n      [HistoryDirectory.name]: HistoryDirectory,\n      [SelectDirectory.name]: SelectDirectory,\n      [SelectTorrent.name]: SelectTorrent\n    },\n    props: {\n      visible: {\n        type: Boolean,\n        default: false\n      },\n      type: {\n        type: String,\n        default: ADD_TASK_TYPE.URI\n      }\n    },\n    data () {\n      return {\n        formLabelWidth: '110px',\n        showAdvanced: false,\n        form: {},\n        rules: {}\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      isMas: () => is.mas(),\n      ...mapState('app', {\n        taskList: state => state.taskList\n      }),\n      ...mapState('preference', {\n        config: state => state.config\n      }),\n      taskType () {\n        return this.type\n      },\n      dialogTop () {\n        return this.showAdvanced ? '8vh' : '15vh'\n      }\n    },\n    watch: {\n      taskType (current, previous) {\n        if (this.visible && previous === ADD_TASK_TYPE.URI) {\n          return\n        }\n\n        if (current === ADD_TASK_TYPE.URI) {\n          setTimeout(() => {\n            this.$refs.uri && this.$refs.uri.focus()\n          }, 50)\n        }\n      },\n      visible (current) {\n        if (current === true) {\n          document.addEventListener('keydown', this.handleHotkey)\n        } else {\n          document.removeEventListener('keydown', this.handleHotkey)\n        }\n      }\n    },\n    methods: {\n      async autofillResourceLink () {\n        const content = await navigator.clipboard.readText()\n        const hasResource = detectResource(content)\n        if (!hasResource) {\n          return\n        }\n\n        if (isEmpty(this.form.uris)) {\n          this.form.uris = content\n        }\n      },\n      beforeClose () {\n        if (isEmpty(this.form.uris) && isEmpty(this.form.torrent)) {\n          this.handleClose()\n        }\n      },\n      handleOpen () {\n        this.form = initTaskForm(this.$store.state)\n        if (this.taskType === ADD_TASK_TYPE.URI) {\n          this.autofillResourceLink()\n          setTimeout(() => {\n            this.$refs.uri && this.$refs.uri.focus()\n          }, 50)\n        }\n      },\n      handleOpened () {\n        this.detectThunderResource(this.form.uris)\n      },\n      handleCancel () {\n        this.$store.dispatch('app/hideAddTaskDialog')\n      },\n      handleClose () {\n        this.$store.dispatch('app/hideAddTaskDialog')\n        this.$store.dispatch('app/updateAddTaskOptions', {})\n      },\n      handleClosed () {\n        this.reset()\n      },\n      handleHotkey (event) {\n        if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {\n          event.preventDefault()\n\n          this.submitForm('taskForm')\n        }\n      },\n      handleTabClick (tab) {\n        this.$store.dispatch('app/changeAddTaskType', tab.name)\n      },\n      handleUriPaste () {\n        setImmediate(() => {\n          const uris = this.$refs.uri.value\n          this.detectThunderResource(uris)\n        })\n      },\n      detectThunderResource (uris = '') {\n        if (uris.includes('thunder://')) {\n          this.$msg({\n            type: 'warning',\n            message: this.$t('task.thunder-link-tips'),\n            duration: 6000\n          })\n        }\n      },\n      handleTorrentChange (torrent, selectedFileIndex) {\n        this.form.torrent = torrent\n        this.form.selectFile = selectedFileIndex\n      },\n      handleHistoryDirectorySelected (dir) {\n        this.form.dir = dir\n      },\n      handleNativeDirectorySelected (dir) {\n        this.form.dir = dir\n        this.$store.dispatch('preference/recordHistoryDirectory', dir)\n      },\n      reset () {\n        this.showAdvanced = false\n        this.form = initTaskForm(this.$store.state)\n      },\n      addTask (type, form) {\n        let payload = null\n        if (type === ADD_TASK_TYPE.URI) {\n          payload = buildUriPayload(form)\n          this.$store.dispatch('task/addUri', payload).catch(err => {\n            this.$msg.error(err.message)\n          })\n        } else if (type === ADD_TASK_TYPE.TORRENT) {\n          payload = buildTorrentPayload(form)\n          this.$store.dispatch('task/addTorrent', payload).catch(err => {\n            this.$msg.error(err.message)\n          })\n        } else if (type === 'metalink') {\n        // @TODO addMetalink\n        } else {\n          console.error('[Motrix] Add task fail', form)\n        }\n      },\n      submitForm (formName) {\n        this.$refs[formName].validate(valid => {\n          if (!valid) {\n            return false\n          }\n\n          try {\n            this.addTask(this.type, this.form)\n\n            this.$store.dispatch('app/hideAddTaskDialog')\n            if (this.form.newTaskShowDownloading) {\n              this.$router.push({\n                path: '/task/active'\n              }).catch(err => {\n                console.log(err)\n              })\n            }\n          } catch (err) {\n            this.$msg.error(this.$t(err.message))\n          }\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.el-dialog.add-task-dialog {\n  max-width: 632px;\n  min-width: 380px;\n  .task-advanced-options .el-form-item:last-of-type {\n    margin-bottom: 0;\n  }\n  .el-tabs__header {\n    user-select: none;\n  }\n  .el-input-number.el-input-number--mini {\n    width: 100%;\n  }\n  .help-link {\n    font-size: 12px;\n    line-height: 14px;\n    padding-top: 7px;\n    > a {\n      color: #909399;\n    }\n  }\n  .el-dialog__footer {\n    padding-top: 20px;\n    background-color: $--add-task-dialog-footer-background;\n    border-radius: 0 0 5px 5px;\n  }\n  .dialog-footer {\n    .chk {\n      float: left;\n      line-height: 28px;\n      &.el-checkbox {\n        & .el-checkbox__input {\n          line-height: 19px;\n        }\n        & .el-checkbox__label {\n          padding-left: 6px;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/Index.vue",
    "content": "<template>\n  <el-container\n    class=\"main panel\"\n    direction=\"horizontal\"\n  >\n    <el-aside\n      width=\"200px\"\n      class=\"subnav hidden-xs-only\"\n    >\n      <mo-task-subnav :current=\"status\" />\n    </el-aside>\n    <el-container\n      class=\"content panel\"\n      direction=\"vertical\"\n    >\n      <el-header\n        class=\"panel-header\"\n        height=\"84\"\n      >\n        <h4 class=\"task-title hidden-xs-only\">{{ title }}</h4>\n        <mo-subnav-switcher\n          :title=\"title\"\n          :subnavs=\"subnavs\"\n          class=\"hidden-sm-and-up\"\n        />\n        <mo-task-actions />\n      </el-header>\n      <el-main class=\"panel-content\">\n        <mo-task-list />\n      </el-main>\n    </el-container>\n  </el-container>\n</template>\n\n<script>\n  import { dialog } from '@electron/remote'\n  import { mapState } from 'vuex'\n\n  import { commands } from '@/components/CommandManager/instance'\n  import { ADD_TASK_TYPE } from '@shared/constants'\n  import TaskSubnav from '@/components/Subnav/TaskSubnav'\n  import TaskActions from '@/components/Task/TaskActions'\n  import TaskList from '@/components/Task/TaskList'\n  import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'\n  import {\n    getTaskUri,\n    parseHeader\n  } from '@shared/utils'\n  import {\n    delayDeleteTaskFiles,\n    showItemInFolder,\n    moveTaskFilesToTrash\n  } from '@/utils/native'\n\n  export default {\n    name: 'mo-content-task',\n    components: {\n      [TaskSubnav.name]: TaskSubnav,\n      [TaskActions.name]: TaskActions,\n      [TaskList.name]: TaskList,\n      [SubnavSwitcher.name]: SubnavSwitcher\n    },\n    props: {\n      status: {\n        type: String,\n        default: 'active'\n      }\n    },\n    computed: {\n      ...mapState('task', {\n        taskList: state => state.taskList,\n        selectedGidList: state => state.selectedGidList,\n        selectedGidListCount: state => state.selectedGidList.length\n      }),\n      ...mapState('preference', {\n        noConfirmBeforeDelete: state => state.config.noConfirmBeforeDeleteTask\n      }),\n      subnavs () {\n        return [\n          {\n            key: 'active',\n            title: this.$t('task.active'),\n            route: '/task/active'\n          },\n          {\n            key: 'waiting',\n            title: this.$t('task.waiting'),\n            route: '/task/waiting'\n          },\n          {\n            key: 'stopped',\n            title: this.$t('task.stopped'),\n            route: '/task/stopped'\n          }\n        ]\n      },\n      title () {\n        const subnav = this.subnavs.find((item) => item.key === this.status)\n        return subnav.title\n      }\n    },\n    watch: {\n      status: 'onStatusChange'\n    },\n    methods: {\n      onStatusChange () {\n        this.changeCurrentList()\n      },\n      changeCurrentList () {\n        this.$store.dispatch('task/changeCurrentList', this.status)\n      },\n      directAddTask (uri, options = {}) {\n        const uris = [uri]\n        const payload = {\n          uris,\n          options: {\n            ...options\n          }\n        }\n        this.$store.dispatch('task/addUri', payload)\n          .catch((err) => {\n            this.$msg.error(err.message)\n          })\n      },\n      showAddTaskDialog (uri, options = {}) {\n        const {\n          header,\n          ...rest\n        } = options\n        console.log('[Motrix] show add task dialog options: ', options)\n\n        const headers = parseHeader(header)\n        const newOptions = {\n          ...rest,\n          ...headers\n        }\n\n        this.$store.dispatch('app/updateAddTaskUrl', uri)\n        this.$store.dispatch('app/updateAddTaskOptions', newOptions)\n        this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.URI)\n      },\n      deleteTaskFiles (task) {\n        try {\n          const result = moveTaskFilesToTrash(task)\n\n          if (!result) {\n            throw new Error('task.remove-task-file-fail')\n          }\n        } catch (err) {\n          this.$msg.error(this.$t(err.message))\n        }\n      },\n      removeTask (task, taskName, isRemoveWithFiles = false) {\n        this.$store.dispatch('task/forcePauseTask', task)\n          .finally(() => {\n            if (isRemoveWithFiles) {\n              this.deleteTaskFiles(task)\n            }\n\n            return this.removeTaskItem(task, taskName)\n          })\n      },\n      removeTaskRecord (task, taskName, isRemoveWithFiles = false) {\n        this.$store.dispatch('task/forcePauseTask', task)\n          .finally(() => {\n            if (isRemoveWithFiles) {\n              this.deleteTaskFiles(task)\n            }\n\n            return this.removeTaskRecordItem(task, taskName)\n          })\n      },\n      async removeTaskItem (task, taskName) {\n        try {\n          await this.$store.dispatch('task/removeTask', task)\n          this.$msg.success(this.$t('task.delete-task-success', {\n            taskName\n          }))\n        } catch ({ code }) {\n          if (code === 1) {\n            this.$msg.error(this.$t('task.delete-task-fail', {\n              taskName\n            }))\n          }\n        }\n      },\n      async removeTaskRecordItem (task, taskName) {\n        try {\n          await this.$store.dispatch('task/removeTaskRecord', task)\n          this.$msg.success(this.$t('task.remove-record-success', {\n            taskName\n          }))\n        } catch ({ code }) {\n          if (code === 1) {\n            this.$msg.error(this.$t('task.remove-record-fail', {\n              taskName\n            }))\n          }\n        }\n      },\n      removeTasks (taskList, isRemoveWithFiles = false) {\n        const gids = taskList.map((task) => task.gid)\n        this.$store.dispatch('task/batchForcePauseTask', gids)\n          .finally(() => {\n            if (isRemoveWithFiles) {\n              this.batchDeleteTaskFiles(taskList)\n            }\n\n            this.removeTaskItems(gids)\n          })\n      },\n      batchDeleteTaskFiles (taskList) {\n        const promises = taskList.map((task, index) => delayDeleteTaskFiles(task, index * 200))\n        Promise.allSettled(promises).then(results => {\n          console.log('[Motrix] batch delete task files: ', results)\n        })\n      },\n      removeTaskItems (gids) {\n        this.$store.dispatch('task/batchRemoveTask', gids)\n          .then(() => {\n            this.$msg.success(this.$t('task.batch-delete-task-success'))\n          })\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.batch-delete-task-fail'))\n            }\n          })\n      },\n      handlePauseTask (payload) {\n        const { task, taskName } = payload\n        this.$msg.info(this.$t('task.download-pause-message', { taskName }))\n        this.$store.dispatch('task/pauseTask', task)\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.pause-task-fail', { taskName }))\n            }\n          })\n      },\n      handleResumeTask (payload) {\n        const { task, taskName } = payload\n        this.$store.dispatch('task/resumeTask', task)\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.resume-task-fail', {\n                taskName\n              }))\n            }\n          })\n      },\n      handleStopTaskSeeding (payload) {\n        const { task } = payload\n        this.$store.dispatch('task/stopSeeding', task)\n        this.$msg.info({\n          message: this.$t('task.bt-stopping-seeding-tip'),\n          duration: 8000\n        })\n      },\n      handleRestartTask (payload) {\n        const { task, taskName, showDialog } = payload\n        const { gid } = task\n        const uri = getTaskUri(task)\n\n        this.$store.dispatch('task/getTaskOption', gid)\n          .then((data) => {\n            console.log('[Motrix] get task option:', data)\n            const { dir, header, split } = data\n            const options = {\n              dir,\n              header,\n              split,\n              out: taskName\n            }\n\n            if (showDialog) {\n              this.showAddTaskDialog(uri, options)\n            } else {\n              this.directAddTask(uri, options)\n              this.$store.dispatch('task/removeTaskRecord', task)\n            }\n          })\n      },\n      handleRevealInFolder (payload) {\n        const { path } = payload\n        showItemInFolder(path, {\n          errorMsg: this.$t('task.file-not-exist')\n        })\n      },\n      handleDeleteTask (payload) {\n        const { task, taskName, deleteWithFiles } = payload\n        const { noConfirmBeforeDelete } = this\n\n        if (noConfirmBeforeDelete) {\n          this.removeTask(task, taskName, deleteWithFiles)\n          return\n        }\n\n        dialog.showMessageBox({\n          type: 'warning',\n          title: this.$t('task.delete-task'),\n          message: this.$t('task.delete-task-confirm', { taskName }),\n          buttons: [this.$t('app.yes'), this.$t('app.no')],\n          cancelId: 1,\n          checkboxLabel: this.$t('task.delete-task-label'),\n          checkboxChecked: deleteWithFiles\n        }).then(({ response, checkboxChecked }) => {\n          if (response === 0) {\n            this.removeTask(task, taskName, checkboxChecked)\n          }\n        })\n      },\n      handleDeleteTaskRecord (payload) {\n        const { task, taskName, deleteWithFiles } = payload\n        const { noConfirmBeforeDelete } = this\n\n        if (noConfirmBeforeDelete) {\n          this.removeTaskRecord(task, taskName, deleteWithFiles)\n          return\n        }\n\n        dialog.showMessageBox({\n          type: 'warning',\n          title: this.$t('task.remove-record'),\n          message: this.$t('task.remove-record-confirm', { taskName }),\n          buttons: [this.$t('app.yes'), this.$t('app.no')],\n          cancelId: 1,\n          checkboxLabel: this.$t('task.remove-record-label'),\n          checkboxChecked: !!deleteWithFiles\n        }).then(({ response, checkboxChecked }) => {\n          if (response === 0) {\n            this.removeTaskRecord(task, taskName, checkboxChecked)\n          }\n        })\n      },\n      handleBatchDeleteTask (payload) {\n        const { deleteWithFiles } = payload\n        const {\n          noConfirmBeforeDelete,\n          selectedGidList,\n          selectedGidListCount,\n          taskList\n        } = this\n        if (selectedGidListCount === 0) {\n          return\n        }\n\n        const selectedTaskList = taskList.filter((task) => {\n          return selectedGidList.includes(task.gid)\n        })\n\n        if (noConfirmBeforeDelete) {\n          this.removeTasks(selectedTaskList, deleteWithFiles)\n          return\n        }\n\n        const count = `${selectedGidListCount}`\n        dialog.showMessageBox({\n          type: 'warning',\n          title: this.$t('task.delete-selected-task'),\n          message: this.$t('task.batch-delete-task-confirm', { count }),\n          buttons: [this.$t('app.yes'), this.$t('app.no')],\n          cancelId: 1,\n          checkboxLabel: this.$t('task.delete-task-label'),\n          checkboxChecked: deleteWithFiles\n        }).then(({ response, checkboxChecked }) => {\n          if (response === 0) {\n            this.removeTasks(selectedTaskList, checkboxChecked)\n          }\n        })\n      },\n      handleCopyTaskLink (payload) {\n        const { task } = payload\n        const uri = getTaskUri(task)\n        navigator.clipboard.writeText(uri)\n          .then(() => {\n            this.$msg.success(this.$t('task.copy-link-success'))\n          })\n      },\n      handleShowTaskInfo (payload) {\n        const { task } = payload\n        this.$store.dispatch('task/showTaskDetail', task)\n      }\n    },\n    created () {\n      this.changeCurrentList()\n    },\n    mounted () {\n      commands.on('pause-task', this.handlePauseTask)\n      commands.on('resume-task', this.handleResumeTask)\n      commands.on('stop-task-seeding', this.handleStopTaskSeeding)\n      commands.on('restart-task', this.handleRestartTask)\n      commands.on('reveal-in-folder', this.handleRevealInFolder)\n      commands.on('delete-task', this.handleDeleteTask)\n      commands.on('delete-task-record', this.handleDeleteTaskRecord)\n      commands.on('batch-delete-task', this.handleBatchDeleteTask)\n      commands.on('copy-task-link', this.handleCopyTaskLink)\n      commands.on('show-task-info', this.handleShowTaskInfo)\n    },\n    destroyed () {\n      commands.off('pause-task', this.handlePauseTask)\n      commands.off('resume-task', this.handleResumeTask)\n      commands.off('stop-task-seeding', this.handleStopTaskSeeding)\n      commands.off('restart-task', this.handleRestartTask)\n      commands.off('reveal-in-folder', this.handleRevealInFolder)\n      commands.off('delete-task', this.handleDeleteTask)\n      commands.off('delete-task-record', this.handleDeleteTaskRecord)\n      commands.off('batch-delete-task', this.handleBatchDeleteTask)\n      commands.off('copy-task-link', this.handleCopyTaskLink)\n      commands.off('show-task-info', this.handleShowTaskInfo)\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Task/SelectTorrent.vue",
    "content": "<template>\n  <el-upload\n    class=\"upload-torrent\"\n    drag\n    action=\"/\"\n    v-if=\"isTorrentsEmpty\"\n    :limit=\"1\"\n    :multiple=\"false\"\n    accept=\".torrent\"\n    :on-change=\"handleChange\"\n    :on-exceed=\"handleExceed\"\n    :auto-upload=\"false\"\n    :show-file-list=\"false\">\n    <i class=\"upload-inbox-icon\"><mo-icon name=\"inbox\" width=\"24\" height=\"24\" /></i>\n    <div class=\"el-upload__text\">\n      {{ $t('task.select-torrent') }}\n      <div class=\"torrent-name\" v-if=\"name\">{{ name }}</div>\n    </div>\n  </el-upload>\n  <div\n    class=\"selective-torrent\"\n    v-else\n  >\n    <el-row class=\"torrent-info\" :gutter=\"12\">\n      <el-col class=\"torrent-name\" :span=\"20\">\n        <el-tooltip class=\"item\" effect=\"dark\" :content=\"name\" placement=\"top\">\n          <span>{{ name }}</span>\n        </el-tooltip>\n      </el-col>\n      <el-col class=\"torrent-actions\" :span=\"4\">\n        <span @click=\"handleTrashClick\">\n          <mo-icon name=\"trash\" width=\"14\" height=\"14\" />\n        </span>\n      </el-col>\n    </el-row>\n    <mo-task-files\n      ref=\"torrentFileList\"\n      mode=\"ADD\"\n      :files=\"files\"\n      :height=\"200\"\n      @selection-change=\"handleSelectionChange\"\n    />\n  </div>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import { remote } from 'parse-torrent'\n  import TaskFiles from '@/components/TaskDetail/TaskFiles'\n  import '@/components/Icons/inbox'\n  import {\n    EMPTY_STRING,\n    NONE_SELECTED_FILES,\n    SELECTED_ALL_FILES\n  } from '@shared/constants'\n  import {\n    buildFileList,\n    listTorrentFiles,\n    bytesToSize,\n    getAsBase64,\n    removeExtensionDot\n  } from '@shared/utils'\n\n  export default {\n    name: 'mo-select-torrent',\n    components: {\n      [TaskFiles.name]: TaskFiles\n    },\n    filters: {\n      bytesToSize,\n      removeExtensionDot\n    },\n    props: {\n    },\n    data () {\n      return {\n        name: EMPTY_STRING,\n        currentTorrent: EMPTY_STRING,\n        files: [],\n        selectedFiles: []\n      }\n    },\n    computed: {\n      ...mapState('app', {\n        torrents: state => state.addTaskTorrents\n      }),\n      ...mapState('preference', {\n        config: state => state.config\n      }),\n      isTorrentsEmpty () {\n        return this.torrents.length === 0\n      }\n    },\n    watch: {\n      torrents (fileList) {\n        if (fileList.length === 0) {\n          this.reset()\n          return\n        }\n\n        const file = fileList[0]\n        if (!file.raw) {\n          return\n        }\n\n        remote(file.raw, { timeout: 60 * 1000 }, (err, parsedTorrent) => {\n          if (err) throw err\n          console.log('[Motrix] parsed torrent: ', parsedTorrent)\n          this.files = listTorrentFiles(parsedTorrent.files)\n          this.$refs.torrentFileList.toggleAllSelection()\n\n          getAsBase64(file.raw, (torrent) => {\n            this.name = file.name\n            this.currentTorrent = torrent\n            this.$emit('change', torrent, SELECTED_ALL_FILES)\n          })\n        })\n      }\n    },\n    methods: {\n      reset () {\n        this.name = EMPTY_STRING\n        this.currentTorrent = EMPTY_STRING\n        this.files = []\n        if (this.$refs.torrentFileList) {\n          this.$refs.torrentFileList.clearSelection()\n        }\n        this.$emit('change', EMPTY_STRING, NONE_SELECTED_FILES)\n      },\n      handleChange (file, fileList) {\n        this.$store.dispatch('app/addTaskAddTorrents', { fileList })\n      },\n      handleExceed (files) {\n        const fileList = buildFileList(files[0])\n        this.$store.dispatch('app/addTaskAddTorrents', { fileList })\n      },\n      handleTrashClick () {\n        this.$store.dispatch('app/addTaskAddTorrents', { fileList: [] })\n      },\n      handleSelectionChange (val) {\n        const { currentTorrent } = this\n        this.$emit('change', currentTorrent, val)\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.upload-torrent {\n  width: 100%;\n  .el-upload, .el-upload-dragger {\n    width: 100%;\n  }\n  .el-upload-dragger {\n    border-radius: 4px;\n    padding: 24px;\n    height: auto;\n  }\n  .upload-inbox-icon {\n    display: inline-block;\n    margin-bottom: 12px;\n  }\n  .torrent-name {\n    margin-top: 4px;\n    font-size: $--font-size-small;\n    color: $--color-text-secondary;\n    line-height: 16px;\n  }\n}\n.selective-torrent {\n  .torrent-name {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n  .torrent-info {\n    margin-bottom: 15px;\n    font-size: 12px;\n    line-height: 16px;\n  }\n  .torrent-actions {\n    text-align: right;\n    line-height: 16px;\n    &> span {\n      cursor: pointer;\n      display: inline-block;\n      vertical-align: middle;\n      height: 14px;\n      padding: 1px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskActions.vue",
    "content": "<template>\n  <div class=\"task-actions\">\n    <el-tooltip\n      class=\"item hidden-md-and-up\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.new-task')\"\n    >\n      <i class=\"task-action\" @click.stop=\"onAddClick\">\n        <mo-icon name=\"menu-add\" width=\"14\" height=\"14\" />\n      </i>\n    </el-tooltip>\n    <el-tooltip\n      class=\"item\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.delete-selected-tasks')\"\n      v-if=\"currentList !== 'stopped'\"\n    >\n      <i\n        class=\"task-action\"\n        :class=\"{ disabled: selectedGidListCount === 0 }\"\n        @click=\"onBatchDeleteClick\">\n        <mo-icon name=\"delete\" width=\"14\" height=\"14\" />\n      </i>\n    </el-tooltip>\n    <el-tooltip\n      class=\"item\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.refresh-list')\"\n    >\n      <i class=\"task-action\" @click=\"onRefreshClick\">\n        <mo-icon name=\"refresh\" width=\"14\" height=\"14\" :spin=\"refreshing\" />\n      </i>\n    </el-tooltip>\n    <el-tooltip\n      class=\"item\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.resume-all-task')\"\n    >\n      <i class=\"task-action\" @click=\"onResumeAllClick\">\n        <mo-icon name=\"task-start-line\" width=\"14\" height=\"14\" />\n      </i>\n    </el-tooltip>\n    <el-tooltip\n      class=\"item\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.pause-all-task')\"\n    >\n      <i class=\"task-action\" @click=\"onPauseAllClick\">\n        <mo-icon name=\"task-pause-line\" width=\"14\" height=\"14\" />\n      </i>\n    </el-tooltip>\n    <el-tooltip\n      class=\"item\"\n      effect=\"dark\"\n      placement=\"bottom\"\n      :content=\"$t('task.purge-record')\"\n      v-if=\"currentList === 'stopped'\"\n    >\n      <i class=\"task-action\" @click=\"onPurgeRecordClick\">\n        <mo-icon name=\"purge\" width=\"14\" height=\"14\" />\n      </i>\n    </el-tooltip>\n  </div>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n\n  import { commands } from '@/components/CommandManager/instance'\n  import { ADD_TASK_TYPE } from '@shared/constants'\n  import { bytesToSize, timeFormat } from '@shared/utils'\n  import '@/components/Icons/menu-add'\n  import '@/components/Icons/refresh'\n  import '@/components/Icons/task-start-line'\n  import '@/components/Icons/task-pause-line'\n  import '@/components/Icons/delete'\n  import '@/components/Icons/purge'\n  import '@/components/Icons/more'\n\n  export default {\n    name: 'mo-task-actions',\n    components: {\n    },\n    props: ['task'],\n    data () {\n      return {\n        refreshing: false\n      }\n    },\n    computed: {\n      ...mapState('task', {\n        currentList: state => state.currentList,\n        selectedGidListCount: state => state.selectedGidList.length\n      })\n    },\n    filters: {\n      bytesToSize,\n      timeFormat\n    },\n    methods: {\n      refreshSpin () {\n        this.t && clearTimeout(this.t)\n\n        this.refreshing = true\n        this.t = setTimeout(() => {\n          this.refreshing = false\n        }, 500)\n      },\n      onBatchDeleteClick (event) {\n        const deleteWithFiles = !!event.shiftKey\n        commands.emit('batch-delete-task', { deleteWithFiles })\n      },\n      onRefreshClick () {\n        this.refreshSpin()\n        this.$store.dispatch('task/fetchList')\n      },\n      onResumeAllClick () {\n        this.$store.dispatch('task/resumeAllTask')\n          .then(() => {\n            this.$msg.success(this.$t('task.resume-all-task-success'))\n          })\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.resume-all-task-fail'))\n            }\n          })\n      },\n      onPauseAllClick () {\n        this.$store.dispatch('task/pauseAllTask')\n          .then(() => {\n            this.$msg.success(this.$t('task.pause-all-task-success'))\n          })\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.pause-all-task-fail'))\n            }\n          })\n      },\n      onPurgeRecordClick () {\n        this.$store.dispatch('task/purgeTaskRecord')\n          .then(() => {\n            this.$msg.success(this.$t('task.purge-record-success'))\n          })\n          .catch(({ code }) => {\n            if (code === 1) {\n              this.$msg.error(this.$t('task.purge-record-fail'))\n            }\n          })\n      },\n      onAddClick () {\n        this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.URI)\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-actions {\n  position: absolute;\n  top: 44px;\n  right: 0;\n  height: 24px;\n  padding: 0;\n  overflow: hidden;\n  user-select: none;\n  cursor: default;\n  text-align: right;\n  color: $--task-action-color;\n  transition: all 0.25s;\n  .task-action {\n    display: inline-block;\n    padding: 5px;\n    margin: 0 4px;\n    font-size: 0;\n    cursor: pointer;\n    outline: none;\n    &:hover {\n      color: $--task-action-hover-color;\n    }\n    &.disabled {\n      color: $--task-action-disabled-color;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskItem.vue",
    "content": "<template>\n  <div :key=\"task.gid\" class=\"task-item\" v-on:dblclick=\"onDbClick\">\n    <div class=\"task-name\" :title=\"taskFullName\">\n      <span>{{ taskFullName }}</span>\n    </div>\n    <mo-task-item-actions mode=\"LIST\" :task=\"task\" />\n    <div class=\"task-progress\">\n      <mo-task-progress\n        :completed=\"Number(task.completedLength)\"\n        :total=\"Number(task.totalLength)\"\n        :status=\"taskStatus\"\n      />\n      <mo-task-progress-info :task=\"task\" />\n    </div>\n  </div>\n</template>\n\n<script>\n  import { checkTaskIsSeeder, getTaskName } from '@shared/utils'\n  import { TASK_STATUS } from '@shared/constants'\n  import { openItem, getTaskFullPath } from '@/utils/native'\n  import TaskItemActions from './TaskItemActions'\n  import TaskProgress from './TaskProgress'\n  import TaskProgressInfo from './TaskProgressInfo'\n\n  export default {\n    name: 'mo-task-item',\n    components: {\n      [TaskItemActions.name]: TaskItemActions,\n      [TaskProgress.name]: TaskProgress,\n      [TaskProgressInfo.name]: TaskProgressInfo\n    },\n    props: {\n      task: {\n        type: Object\n      }\n    },\n    computed: {\n      taskFullName () {\n        return getTaskName(this.task, {\n          defaultName: this.$t('task.get-task-name'),\n          maxLen: -1\n        })\n      },\n      taskName () {\n        return getTaskName(this.task, {\n          defaultName: this.$t('task.get-task-name')\n        })\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      taskStatus () {\n        const { task, isSeeder } = this\n        if (isSeeder) {\n          return TASK_STATUS.SEEDING\n        } else {\n          return task.status\n        }\n      }\n    },\n    methods: {\n      onDbClick () {\n        const { status } = this.task\n        const { COMPLETE, WAITING, PAUSED } = TASK_STATUS\n        if (status === COMPLETE) {\n          this.openTask()\n        } else if ([WAITING, PAUSED].includes(status) !== -1) {\n          this.toggleTask()\n        }\n      },\n      async openTask () {\n        const { taskName } = this\n        this.$msg.info(this.$t('task.opening-task-message', { taskName }))\n        const fullPath = getTaskFullPath(this.task)\n        const result = await openItem(fullPath)\n        if (result) {\n          this.$msg.error(this.$t('task.file-not-exist'))\n        }\n      },\n      toggleTask () {\n        this.$store.dispatch('task/toggleTask', this.task)\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-item {\n  position: relative;\n  min-height: 78px;\n  padding: 16px 12px;\n  background-color: $--task-item-background;\n  border: 1px solid $--task-item-border-color;\n  border-radius: 6px;\n  margin-bottom: 16px;\n  transition: $--border-transition-base;\n  &:hover {\n    border-color: $--task-item-hover-border-color;\n  }\n  .task-item-actions {\n    position: absolute;\n    top: 16px;\n    right: 12px;\n  }\n}\n.selected .task-item {\n  border-color: $--task-item-hover-border-color;\n}\n.task-name {\n  color: #505753;\n  margin-bottom: 1.5rem;\n  margin-right: 200px;\n  word-break: break-all;\n  min-height: 26px;\n  &> span {\n    font-size: 14px;\n    line-height: 26px;\n    overflow : hidden;\n    text-overflow: ellipsis;\n    display: -webkit-box;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskItemActions.vue",
    "content": "<template>\n  <ul :key=\"task.gid\" class=\"task-item-actions\" v-on:dblclick.stop=\"() => null\">\n    <li v-for=\"action in taskActions\" :key=\"action\" class=\"task-item-action\">\n      <i v-if=\"action ==='PAUSE'\" @click.stop=\"onPauseClick\">\n        <mo-icon name=\"task-pause-line\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action ==='STOP'\" @click.stop=\"onStopClick\">\n        <mo-icon name=\"task-stop-line\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action === 'RESUME'\" @click.stop=\"onResumeClick\">\n        <mo-icon name=\"task-start-line\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action === 'RESTART'\" @click.stop=\"onRestartClick\">\n        <mo-icon name=\"task-restart\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action === 'DELETE'\" @click.stop=\"onDeleteClick\">\n        <mo-icon name=\"delete\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action === 'TRASH'\" @click.stop=\"onTrashClick\">\n        <mo-icon name=\"trash\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action ==='FOLDER'\" @click.stop=\"onFolderClick\">\n        <mo-icon name=\"folder\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action ==='LINK'\" @click.stop=\"onLinkClick\">\n        <mo-icon name=\"link\" width=\"14\" height=\"14\" />\n      </i>\n      <i v-if=\"action ==='INFO'\" @click.stop=\"onInfoClick\">\n        <mo-icon name=\"info-circle\" width=\"14\" height=\"14\" />\n      </i>\n    </li>\n  </ul>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import is from 'electron-is'\n\n  import { commands } from '@/components/CommandManager/instance'\n  import { TASK_STATUS } from '@shared/constants'\n  import {\n    checkTaskIsSeeder,\n    getTaskName\n  } from '@shared/utils'\n  import { getTaskFullPath } from '@/utils/native'\n  import '@/components/Icons/task-start-line'\n  import '@/components/Icons/task-pause-line'\n  import '@/components/Icons/task-stop-line'\n  import '@/components/Icons/task-restart'\n  import '@/components/Icons/delete'\n  import '@/components/Icons/folder'\n  import '@/components/Icons/link'\n  import '@/components/Icons/info-circle'\n  import '@/components/Icons/trash'\n\n  const taskActionsMap = {\n    [TASK_STATUS.ACTIVE]: ['PAUSE', 'DELETE'],\n    [TASK_STATUS.PAUSED]: ['RESUME', 'DELETE'],\n    [TASK_STATUS.WAITING]: ['RESUME', 'DELETE'],\n    [TASK_STATUS.ERROR]: ['RESTART', 'TRASH'],\n    [TASK_STATUS.COMPLETE]: ['RESTART', 'TRASH'],\n    [TASK_STATUS.REMOVED]: ['RESTART', 'TRASH'],\n    [TASK_STATUS.SEEDING]: ['STOP', 'DELETE']\n  }\n\n  export default {\n    name: 'mo-task-item-actions',\n    props: {\n      mode: {\n        type: String,\n        default: 'LIST',\n        validator: function (value) {\n          return ['LIST', 'DETAIL'].indexOf(value) !== -1\n        }\n      },\n      task: {\n        type: Object,\n        required: true\n      }\n    },\n    computed: {\n      ...mapState('preference', {\n        noConfirmBeforeDelete: state => state.config.noConfirmBeforeDeleteTask\n      }),\n      taskName () {\n        return getTaskName(this.task)\n      },\n      path () {\n        return getTaskFullPath(this.task)\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      taskStatus () {\n        const { task, isSeeder } = this\n        if (isSeeder) {\n          return TASK_STATUS.SEEDING\n        } else {\n          return task.status\n        }\n      },\n      taskCommonActions () {\n        const { mode } = this\n        const result = is.renderer() ? ['FOLDER'] : []\n\n        switch (mode) {\n        case 'LIST':\n          result.push('LINK', 'INFO')\n          break\n        case 'DETAIL':\n          result.push('LINK')\n          break\n        }\n\n        return result\n      },\n      taskActions () {\n        const { taskStatus, taskCommonActions } = this\n        const actions = taskActionsMap[taskStatus] || []\n        const result = [...actions, ...taskCommonActions].reverse()\n        return result\n      }\n    },\n    methods: {\n      onResumeClick () {\n        const { task, taskName } = this\n        commands.emit('resume-task', {\n          task,\n          taskName\n        })\n      },\n      onRestartClick (event) {\n        const { task, taskName } = this\n        const { status } = task\n        const showDialog = status === TASK_STATUS.COMPLETE || !!event.altKey\n        commands.emit('restart-task', {\n          task,\n          taskName,\n          showDialog\n        })\n      },\n      onPauseClick () {\n        const { task, taskName } = this\n        commands.emit('pause-task', {\n          task,\n          taskName\n        })\n      },\n      onStopClick () {\n        if (!this.isSeeder) {\n          return\n        }\n\n        const { task } = this\n        commands.emit('stop-task-seeding', { task })\n      },\n      onDeleteClick (event) {\n        const { task, taskName } = this\n        const deleteWithFiles = !!event.shiftKey\n        commands.emit('delete-task', {\n          task,\n          taskName,\n          deleteWithFiles\n        })\n      },\n      onTrashClick (event) {\n        const { task, taskName } = this\n        const deleteWithFiles = !!event.shiftKey\n        commands.emit('delete-task-record', {\n          task,\n          taskName,\n          deleteWithFiles\n        })\n      },\n      onFolderClick () {\n        const { path } = this\n        commands.emit('reveal-in-folder', { path })\n      },\n      onLinkClick () {\n        const { task } = this\n        commands.emit('copy-task-link', { task })\n      },\n      onInfoClick () {\n        const { task } = this\n        commands.emit('show-task-info', { task })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-item-actions {\n  // width: 28px;\n  height: 24px;\n  padding: 0 10px;\n  margin: 0;\n  overflow: hidden;\n  user-select: none;\n  cursor: default;\n  text-align: right;\n  direction: rtl;\n  border: 1px solid $--task-item-action-border-color;\n  color: $--task-item-action-color;\n  background-color: $--task-item-action-background;\n  border-radius: 14px;\n  transition: $--all-transition;\n  &:hover {\n    border-color: $--task-item-action-hover-border-color;\n    color: $--task-item-action-hover-color;\n    background-color: $--task-item-action-hover-background;\n    width: auto;\n  }\n  &> .task-item-action {\n    display: inline-block;\n    padding: 5px;\n    margin: 0 4px;\n    font-size: 0;\n    cursor: pointer;\n    i {\n      display: inline-block;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskList.vue",
    "content": "<template>\n  <mo-drag-select\n    class=\"task-list\"\n    v-if=\"taskList.length > 0\"\n    attribute=\"attr\"\n    @change=\"handleDragSelectChange\"\n  >\n    <div\n      v-for=\"item in taskList\"\n      :key=\"item.gid\"\n      :attr=\"item.gid\"\n      :class=\"getItemClass(item)\"\n    >\n      <mo-task-item\n        :task=\"item\"\n      />\n    </div>\n  </mo-drag-select>\n  <div class=\"no-task\" v-else>\n    <div class=\"no-task-inner\">\n      {{ $t('task.no-task') }}\n    </div>\n  </div>\n</template>\n\n<script>\n  import { mapState } from 'vuex'\n  import { cloneDeep } from 'lodash'\n  import DragSelect from '@/components/DragSelect/Index'\n  import TaskItem from './TaskItem'\n\n  export default {\n    name: 'mo-task-list',\n    components: {\n      [DragSelect.name]: DragSelect,\n      [TaskItem.name]: TaskItem\n    },\n    data () {\n      const selectedList = cloneDeep(this.$store.state.task.selectedList) || []\n      return {\n        selectedList\n      }\n    },\n    computed: {\n      ...mapState('task', {\n        taskList: state => state.taskList,\n        selectedGidList: state => state.selectedGidList\n      })\n    },\n    methods: {\n      handleDragSelectChange (selectedList) {\n        this.selectedList = selectedList\n        this.$store.dispatch('task/selectTasks', cloneDeep(selectedList))\n      },\n      getItemClass (item) {\n        const isSelected = this.selectedList.includes(item.gid)\n        return {\n          selected: isSelected\n        }\n      }\n    },\n    watch: {\n      selectedGidList (newVal) {\n        this.selectedList = newVal\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-list {\n  padding: 16px 16px 64px;\n  min-height: 100%;\n  box-sizing: border-box;\n}\n.no-task {\n  display: flex;\n  height: 100%;\n  text-align: center;\n  align-items: center;\n  justify-content: center;\n  font-size: 14px;\n  color: #555;\n  user-select: none;\n}\n.no-task-inner {\n  width: 100%;\n  padding-top: 360px;\n  background: transparent url('~@/assets/no-task.svg') top center no-repeat;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskProgress.vue",
    "content": "<template>\n  <el-progress\n    v-if=\"isActive\"\n    :percentage=\"percent\"\n    :show-text=\"false\"\n    status=\"success\"\n    :color=\"color\">\n  </el-progress>\n  <el-progress\n    v-else\n    :percentage=\"percent\"\n    :show-text=\"false\"\n    :color=\"color\">\n  </el-progress>\n</template>\n\n<script>\n  import { TASK_STATUS } from '@shared/constants'\n  import { calcProgress } from '@shared/utils'\n  import colors from '@shared/colors'\n\n  export default {\n    name: 'mo-task-progress',\n    props: {\n      total: {\n        type: Number\n      },\n      completed: {\n        type: Number\n      },\n      status: {\n        type: String,\n        default: TASK_STATUS.ACTIVE\n      }\n    },\n    computed: {\n      isActive () {\n        return this.status === TASK_STATUS.ACTIVE\n      },\n      percent () {\n        return calcProgress(this.total, this.completed)\n      },\n      color () {\n        return colors[this.status]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskProgressInfo.vue",
    "content": "<template>\n  <el-row class=\"task-progress-info\">\n    <el-col\n      class=\"task-progress-info-left\"\n      :xs=\"12\"\n      :sm=\"7\"\n      :md=\"6\"\n      :lg=\"6\"\n    >\n      <div v-if=\"task.completedLength > 0 || task.totalLength > 0\">\n        <span>{{ task.completedLength | bytesToSize(2) }}</span>\n        <span v-if=\"task.totalLength > 0\"> / {{ task.totalLength | bytesToSize(2) }}</span>\n      </div>\n    </el-col>\n    <el-col\n      class=\"task-progress-info-right\"\n      v-if=\"isActive\"\n      :xs=\"12\"\n      :sm=\"17\"\n      :md=\"18\"\n      :lg=\"18\"\n    >\n      <div class=\"task-speed-info\">\n        <div class=\"task-speed-text\" v-if=\"isBT\">\n          <i><mo-icon name=\"arrow-up\" width=\"10\" height=\"14\" /></i>\n          <span>{{ task.uploadSpeed | bytesToSize }}/s</span>\n        </div>\n        <div class=\"task-speed-text\">\n          <i><mo-icon name=\"arrow-down\" width=\"10\" height=\"14\" /></i>\n          <span>{{ task.downloadSpeed | bytesToSize }}/s</span>\n        </div>\n        <div class=\"task-speed-text hidden-sm-and-down\" v-if=\"remaining > 0\">\n          <span>\n            {{\n              remaining | timeFormat({\n                prefix: $t('task.remaining-prefix'),\n                i18n: {\n                  'gt1d': $t('app.gt1d'),\n                  'hour': $t('app.hour'),\n                  'minute': $t('app.minute'),\n                  'second': $t('app.second')\n                }\n              })\n            }}\n          </span>\n        </div>\n        <div class=\"task-speed-text hidden-sm-and-down\" v-if=\"isBT\">\n          <i><mo-icon name=\"magnet\" width=\"10\" height=\"14\" /></i>\n          <span>{{ task.numSeeders }}</span>\n        </div>\n        <div class=\"task-speed-text hidden-sm-and-down\">\n          <i><mo-icon name=\"node\" width=\"10\" height=\"14\" /></i>\n          <span>{{ task.connections }}</span>\n        </div>\n      </div>\n    </el-col>\n  </el-row>\n</template>\n\n<script>\n  import {\n    bytesToSize,\n    checkTaskIsBT,\n    checkTaskIsSeeder,\n    timeFormat,\n    timeRemaining\n  } from '@shared/utils'\n  import { TASK_STATUS } from '@shared/constants'\n  import '@/components/Icons/arrow-up'\n  import '@/components/Icons/arrow-down'\n  import '@/components/Icons/node'\n  import '@/components/Icons/magnet'\n\n  export default {\n    name: 'mo-task-progress-info',\n    props: {\n      task: {\n        type: Object\n      }\n    },\n    computed: {\n      isActive () {\n        return this.task.status === TASK_STATUS.ACTIVE\n      },\n      isBT () {\n        return checkTaskIsBT(this.task)\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      remaining () {\n        const { totalLength, completedLength, downloadSpeed } = this.task\n        return timeRemaining(totalLength, completedLength, downloadSpeed)\n      }\n    },\n    filters: {\n      bytesToSize,\n      timeFormat\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-progress-info {\n  font-size: 0.75rem;\n  line-height: 0.875rem;\n  min-height: 0.875rem;\n  color: #9B9B9B;\n  margin-top: 0.5rem;\n  i {\n    font-style: normal;\n  }\n}\n.task-progress-info-left {\n  min-height: 0.875rem;\n  text-align: left;\n}\n.task-progress-info-right {\n  min-height: 0.875rem;\n  text-align: right;\n}\n.task-speed-info {\n  font-size: 0;\n  & > .task-speed-text {\n    margin-left: 0.375rem;\n    font-size: 0;\n    line-height: 0.875rem;\n    vertical-align: middle;\n    display: inline-block;\n    &:first-of-type {\n      margin-left: 0;\n    }\n    & > i, & > span {\n      height: 0.875rem;\n      line-height: 0.875rem;\n      display: inline-block;\n      vertical-align: middle;\n    }\n    & > i {\n      margin-right: 0.125rem;\n    }\n    & > span {\n      font-size: 0.75rem;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/Task/TaskStatus.vue",
    "content": "<template>\n  <el-tag :effect=\"theme\" class=\"tag-task-status\" :type=\"type\">\n    {{ status && status.toUpperCase() }}\n  </el-tag>\n</template>\n\n<script>\n  import { APP_THEME, TASK_STATUS } from '@shared/constants'\n  import colors from '@shared/colors'\n\n  const statusTypeMap = {\n    [TASK_STATUS.ACTIVE]: 'success',\n    [TASK_STATUS.WAITING]: 'info',\n    [TASK_STATUS.PAUSED]: 'info',\n    [TASK_STATUS.ERROR]: 'danger',\n    [TASK_STATUS.COMPLETE]: 'success',\n    [TASK_STATUS.REMOVED]: 'info',\n    [TASK_STATUS.SEEDING]: 'success'\n  }\n\n  export default {\n    name: 'mo-task-status',\n    props: {\n      theme: {\n        type: String,\n        default: APP_THEME.DARK,\n        validator: function (value) {\n          return [APP_THEME.LIGHT, APP_THEME.DARK].indexOf(value) !== -1\n        }\n      },\n      status: {\n        type: String,\n        default: TASK_STATUS.ACTIVE\n      }\n    },\n    computed: {\n      type () {\n        return statusTypeMap[this.status]\n      },\n      color () {\n        return colors[this.status]\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/Index.vue",
    "content": "<template>\n  <el-drawer\n    custom-class=\"panel task-detail-drawer\"\n    size=\"61.8%\"\n    v-if=\"gid\"\n    :title=\"$t('task.task-detail-title')\"\n    :with-header=\"true\"\n    :show-close=\"true\"\n    :destroy-on-close=\"true\"\n    :visible=\"visible\"\n    :before-close=\"handleClose\"\n    @closed=\"handleClosed\"\n  >\n    <el-tabs\n      tab-position=\"top\"\n      class=\"task-detail-tab\"\n      value=\"general\"\n      :before-leave=\"handleTabBeforeLeave\"\n      @tab-click=\"handleTabClick\"\n    >\n      <el-tab-pane name=\"general\">\n        <span class=\"task-detail-tab-label\" slot=\"label\"><i class=\"el-icon-info\"></i></span>\n        <mo-task-general :task=\"task\" />\n      </el-tab-pane>\n      <el-tab-pane name=\"activity\" lazy>\n        <span class=\"task-detail-tab-label\" slot=\"label\"><i class=\"el-icon-s-grid\"></i></span>\n        <mo-task-activity ref=\"taskGraphic\" :task=\"task\" />\n      </el-tab-pane>\n      <el-tab-pane name=\"trackers\" lazy v-if=\"isBT\">\n        <span class=\"task-detail-tab-label\" slot=\"label\"><i class=\"el-icon-discover\"></i></span>\n        <mo-task-trackers :task=\"task\" />\n      </el-tab-pane>\n      <el-tab-pane name=\"peers\" lazy v-if=\"isBT\">\n        <span class=\"task-detail-tab-label\" slot=\"label\"><i class=\"el-icon-s-custom\"></i></span>\n        <mo-task-peers :peers=\"peers\" />\n      </el-tab-pane>\n      <el-tab-pane name=\"files\" lazy>\n        <span class=\"task-detail-tab-label\" slot=\"label\"><i class=\"el-icon-files\"></i></span>\n        <mo-task-files\n          ref=\"detailFileList\"\n          mode=\"DETAIL\"\n          :files=\"fileList\"\n          @selection-change=\"handleSelectionChange\"\n        />\n      </el-tab-pane>\n    </el-tabs>\n    <div class=\"task-detail-actions\">\n      <div class=\"action-wrapper action-wrapper-left\" v-if=\"optionsChanged\">\n        <el-button @click=\"resetChanged\">\n          {{$t('app.reset')}}\n        </el-button>\n      </div>\n      <div class=\"action-wrapper action-wrapper-center\">\n        <mo-task-item-actions mode=\"DETAIL\" :task=\"task\" />\n      </div>\n      <div class=\"action-wrapper action-wrapper-right\" v-if=\"optionsChanged\">\n        <el-button type=\"primary\" @click=\"saveChanged\">\n          {{$t('app.save')}}\n        </el-button>\n      </div>\n    </div>\n  </el-drawer>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { debounce, merge } from 'lodash'\n  import {\n    calcFormLabelWidth,\n    checkTaskIsBT,\n    checkTaskIsSeeder,\n    getFileName,\n    getFileExtension\n  } from '@shared/utils'\n  import {\n    EMPTY_STRING,\n    NONE_SELECTED_FILES,\n    SELECTED_ALL_FILES,\n    TASK_STATUS\n  } from '@shared/constants'\n  import TaskItemActions from '@/components/Task/TaskItemActions'\n  import TaskGeneral from './TaskGeneral'\n  import TaskActivity from './TaskActivity'\n  import TaskTrackers from './TaskTrackers'\n  import TaskPeers from './TaskPeers'\n  import TaskFiles from './TaskFiles'\n\n  const cached = {\n    files: []\n  }\n\n  export default {\n    name: 'mo-task-detail',\n    components: {\n      [TaskItemActions.name]: TaskItemActions,\n      [TaskGeneral.name]: TaskGeneral,\n      [TaskActivity.name]: TaskActivity,\n      [TaskTrackers.name]: TaskTrackers,\n      [TaskPeers.name]: TaskPeers,\n      [TaskFiles.name]: TaskFiles\n    },\n    props: {\n      gid: {\n        type: String\n      },\n      task: {\n        type: Object\n      },\n      files: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      },\n      peers: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      },\n      visible: {\n        type: Boolean,\n        default: false\n      }\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      return {\n        form: {},\n        formLabelWidth: calcFormLabelWidth(locale),\n        locale,\n        activeTab: 'general',\n        graphicWidth: 0,\n        optionsChanged: false,\n        filesSelection: EMPTY_STRING,\n        selectionChangedCount: 0\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      isBT () {\n        return checkTaskIsBT(this.task)\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      taskStatus () {\n        const { task, isSeeder } = this\n        if (isSeeder) {\n          return TASK_STATUS.SEEDING\n        } else {\n          return task.status\n        }\n      },\n      fileList () {\n        const { files } = this\n        const result = files.map((item) => {\n          const name = getFileName(item.path)\n          const extension = getFileExtension(name)\n          return {\n            idx: Number(item.index),\n            selected: item.selected === 'true',\n            path: item.path,\n            name,\n            extension: `.${extension}`,\n            length: parseInt(item.length, 10),\n            completedLength: item.completedLength\n          }\n        })\n        merge(cached.files, result)\n        return cached.files\n      },\n      selectedFileList () {\n        const { fileList } = this\n        const result = fileList.filter((item) => item.selected)\n\n        return result\n      }\n    },\n    mounted () {\n      window.addEventListener('resize', this.handleAppResize)\n    },\n    destroyed () {\n      window.removeEventListener('resize', this.handleAppResize)\n      cached.files = []\n    },\n    watch: {\n      gid () {\n        cached.files = []\n      }\n    },\n    methods: {\n      handleClose (done) {\n        window.removeEventListener('resize', this.handleAppResize)\n        this.$store.dispatch('task/hideTaskDetail')\n      },\n      handleClosed (done) {\n        this.$store.dispatch('task/updateCurrentTaskGid', EMPTY_STRING)\n        this.$store.dispatch('task/updateCurrentTaskItem', null)\n        this.optionsChanged = false\n        this.resetFaskFilesSelection()\n      },\n      handleTabBeforeLeave (activeName, oldActiveName) {\n        this.activeTab = activeName\n        this.optionsChanged = false\n        switch (oldActiveName) {\n        case 'peers':\n          this.$store.dispatch('task/toggleEnabledFetchPeers', false)\n          break\n        case 'files':\n          this.resetFaskFilesSelection()\n          break\n        }\n      },\n      handleTabClick (tab) {\n        const { name } = tab\n        switch (name) {\n        case 'peers':\n          this.$store.dispatch('task/toggleEnabledFetchPeers', true)\n          break\n        case 'files':\n          setImmediate(() => {\n            this.updateFilesListSelection()\n          })\n          break\n        }\n      },\n      resetChanged () {\n        const { activeTab } = this\n        switch (activeTab) {\n        case 'files':\n          this.resetFaskFilesSelection()\n          this.updateFilesListSelection()\n          break\n        }\n        this.optionsChanged = false\n      },\n      saveChanged () {\n        const { activeTab } = this\n        switch (activeTab) {\n        case 'files':\n          this.saveFaskFilesSelection()\n          break\n        }\n        this.optionsChanged = false\n      },\n      handleAppResize () {\n        debounce(() => {\n          console.log('resize===>', this.activeTab, this.$refs.taskGraphic)\n          if (this.activeTab === 'activity' && this.$refs.taskGraphic) {\n            this.$refs.taskGraphic.updateGraphicWidth()\n          }\n        }, 250)\n      },\n      updateFilesListSelection () {\n        if (!this.$refs.detailFileList) {\n          return\n        }\n\n        const { selectedFileList } = this\n        this.$refs.detailFileList.toggleSelection(selectedFileList)\n      },\n      handleSelectionChange (val) {\n        this.filesSelection = val\n        this.selectionChangedCount += 1\n        if (this.selectionChangedCount > 1) {\n          this.optionsChanged = true\n        }\n      },\n      resetFaskFilesSelection () {\n        this.filesSelection = EMPTY_STRING\n        this.selectionChangedCount = 0\n      },\n      saveFaskFilesSelection () {\n        const { gid, filesSelection } = this\n        if (filesSelection === NONE_SELECTED_FILES) {\n          this.$msg.warning(this.$t('task.select-at-least-one'))\n          return\n        }\n\n        const options = {\n          selectFile: filesSelection !== SELECTED_ALL_FILES ? filesSelection : EMPTY_STRING\n        }\n        this.$store.dispatch('task/changeTaskOption', { gid, options })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.task-detail-drawer {\n  min-width: 478px;\n  .el-drawer__header {\n    padding-top: 2rem;\n    margin-bottom: 0;\n  }\n  .el-drawer__body {\n    position: relative;\n    overflow: hidden;\n  }\n  .task-detail-actions {\n    position: sticky;\n    left: 0;\n    bottom: 1rem;\n    z-index: inherit;\n    width: 100%;\n    text-align: center;\n    font-size: 0;\n    padding: 0 1.25rem;\n    display: flex;\n    align-content: space-between;\n    justify-content: space-between;\n    .task-item-actions {\n      display: inline-block;\n      &> .task-item-action {\n        margin: 0 0.5rem;\n      }\n    }\n  }\n  .task-detail-drawer-title {\n    &> span, &> ul {\n      vertical-align: middle;\n    }\n  }\n  .action-wrapper {\n    flex: 1;\n  }\n  .action-wrapper-left {\n    text-align: left;\n  }\n  .action-wrapper-center {\n    padding: 1px 0;\n    &> .task-item-actions {\n      margin: 0 auto;\n    }\n  }\n  .action-wrapper-right {\n    text-align: right;\n  }\n}\n\n.task-detail-tab {\n  height: 100%;\n  padding: 0.5rem 1.25rem 3.125rem;\n  display: flex;\n  flex-direction: column;\n  .task-detail-tab-label {\n    padding: 0 0.75rem;\n  }\n  .el-tabs__content {\n    position: relative;\n    height: 100%;\n  }\n  .el-tab-pane {\n    overflow-x: hidden;\n    overflow-y: auto;\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n  }\n}\n\n.tab-panel-actions {\n  display: flex;\n  justify-content: space-between;\n  position: absolute;\n  bottom: -28px;\n  left: 0;\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/TaskActivity.vue",
    "content": "<template>\n  <el-form\n    class=\"mo-task-activity\"\n    ref=\"form\"\n    :model=\"form\"\n    :label-width=\"formLabelWidth\"\n    v-if=\"task\"\n  >\n    <div class=\"graphic-box\" ref=\"graphicBox\">\n      <mo-task-graphic\n        :outerWidth=\"graphicWidth\"\n        :bitfield=\"task.bitfield\"\n        v-if=\"graphicWidth > 0\"\n      />\n    </div>\n    <el-form-item :label=\"`${$t('task.task-progress-info')}: `\">\n      <div class=\"form-static-value\" style=\"overflow: hidden\">\n        <el-row :gutter=\"12\">\n          <el-col :span=\"18\">\n            <div class=\"progress-wrapper\">\n              <mo-task-progress\n                :completed=\"Number(task.completedLength)\"\n                :total=\"Number(task.totalLength)\"\n                :status=\"taskStatus\"\n              />\n            </div>\n          </el-col>\n          <el-col :span=\"5\">\n            {{ percent }}\n          </el-col>\n        </el-row>\n      </div>\n    </el-form-item>\n    <el-form-item>\n      <div class=\"form-static-value\">\n        <span>{{ task.completedLength | bytesToSize(2) }}</span>\n        <span v-if=\"task.totalLength > 0\"> / {{ task.totalLength | bytesToSize(2) }}</span>\n        <span class=\"task-time-remaining\" v-if=\"isActive && remaining > 0\">\n          {{\n            remaining | timeFormat({\n              prefix: $t('task.remaining-prefix'),\n              i18n: {\n                'gt1d': $t('app.gt1d'),\n                'hour': $t('app.hour'),\n                'minute': $t('app.minute'),\n                'second': $t('app.second')\n              }\n            })\n          }}\n        </span>\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-num-seeders')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.numSeeders }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-connections')}: `\">\n      <div class=\"form-static-value\">\n        {{ task.connections }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-download-speed')}: `\">\n      <div class=\"form-static-value\">\n        <span>{{ task.downloadSpeed | bytesToSize }}/s</span>\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-upload-speed')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        <span>{{ task.uploadSpeed | bytesToSize }}/s</span>\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-upload-length')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        <span>{{ task.uploadLength | bytesToSize }}</span>\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-ratio')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ ratio }}\n      </div>\n    </el-form-item>\n  </el-form>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import {\n    bytesToSize,\n    calcFormLabelWidth,\n    calcProgress,\n    calcRatio,\n    checkTaskIsBT,\n    checkTaskIsSeeder,\n    timeFormat,\n    timeRemaining\n  } from '@shared/utils'\n  import { TASK_STATUS } from '@shared/constants'\n  import TaskGraphic from '@/components/TaskGraphic/Index'\n  import TaskProgress from '@/components/Task/TaskProgress'\n\n  export default {\n    name: 'mo-task-activity',\n    components: {\n      [TaskGraphic.name]: TaskGraphic,\n      [TaskProgress.name]: TaskProgress\n    },\n    props: {\n      gid: {\n        type: String\n      },\n      task: {\n        type: Object\n      },\n      files: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      },\n      peers: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      },\n      visible: {\n        type: Boolean,\n        default: false\n      }\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      return {\n        form: {},\n        formLabelWidth: calcFormLabelWidth(locale),\n        locale,\n        graphicWidth: 0\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      isBT () {\n        return checkTaskIsBT(this.task)\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      taskStatus () {\n        const { task, isSeeder } = this\n        if (isSeeder) {\n          return TASK_STATUS.SEEDING\n        } else {\n          return task.status\n        }\n      },\n      isActive () {\n        return this.taskStatus === TASK_STATUS.ACTIVE\n      },\n      percent () {\n        const { totalLength, completedLength } = this.task\n        const percent = calcProgress(totalLength, completedLength)\n        return `${percent}%`\n      },\n      remaining () {\n        const { totalLength, completedLength, downloadSpeed } = this.task\n        return timeRemaining(totalLength, completedLength, downloadSpeed)\n      },\n      ratio () {\n        if (!this.isBT) {\n          return 0\n        }\n\n        const { totalLength, uploadLength } = this.task\n        const ratio = calcRatio(totalLength, uploadLength)\n        return ratio\n      }\n    },\n    filters: {\n      bytesToSize,\n      timeFormat\n    },\n    mounted () {\n      setImmediate(() => {\n        this.updateGraphicWidth()\n      })\n    },\n    methods: {\n      updateGraphicWidth () {\n        if (!this.$refs.graphicBox) {\n          return\n        }\n        this.graphicWidth = this.calcInnerWidth(this.$refs.graphicBox)\n      },\n      calcInnerWidth (ele) {\n        if (!ele) {\n          return 0\n        }\n\n        const style = getComputedStyle(ele, null)\n        const width = parseInt(style.width, 10)\n        const paddingLeft = parseInt(style.paddingLeft, 10)\n        const paddingRight = parseInt(style.paddingRight, 10)\n        return width - paddingLeft - paddingRight\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.progress-wrapper {\n  padding: 0.6875rem 0 0 0;\n}\n\n.task-time-remaining {\n  margin-left: 1rem;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/TaskFiles.vue",
    "content": "<template>\n  <div class=\"mo-task-files\" v-if=\"files\">\n    <div class=\"mo-table-wrapper\">\n      <el-table\n        stripe\n        ref=\"torrentTable\"\n        :height=\"height\"\n        :data=\"files\"\n        tooltip-effect=\"dark\"\n        style=\"width: 100%\"\n        @row-dblclick=\"handleRowDbClick\"\n        @selection-change=\"handleSelectionChange\">\n        <el-table-column\n          type=\"selection\"\n          width=\"42\">\n        </el-table-column>\n        <el-table-column\n          :label=\"$t('task.file-name')\"\n          min-width=\"200\"\n          show-overflow-tooltip>\n          <template slot-scope=\"scope\">{{ scope.row.name }}</template>\n        </el-table-column>\n        <el-table-column\n          :label=\"$t('task.file-extension')\"\n          width=\"80\">\n          <template slot-scope=\"scope\">{{ scope.row.extension | removeExtensionDot }}</template>\n        </el-table-column>\n        <el-table-column\n          v-if=\"mode === 'DETAIL'\"\n          :label=\"`%`\"\n          align=\"right\"\n          width=\"50\">\n          <template slot-scope=\"scope\">{{ calcProgress(scope.row.length, scope.row.completedLength, 1) }}</template>\n        </el-table-column>\n        <el-table-column\n          v-if=\"mode === 'DETAIL'\"\n          :label=\"`✓`\"\n          align=\"right\"\n          width=\"85\">\n          <template slot-scope=\"scope\">{{ scope.row.completedLength | bytesToSize }}</template>\n        </el-table-column>\n        <el-table-column\n          :label=\"$t('task.file-size')\"\n          align=\"right\"\n          width=\"85\">\n          <template slot-scope=\"scope\">{{ scope.row.length | bytesToSize }}</template>\n        </el-table-column>\n      </el-table>\n    </div>\n    <el-row class=\"file-filters\" :gutter=\"12\">\n      <el-col\n        class=\"quick-filters\"\n        :xs=\"24\"\n        :sm=\"8\"\n        :md=\"8\"\n        :lg=\"8\"\n      >\n        <el-button-group>\n          <el-button @click=\"toggleVideoSelection()\">\n            <mo-icon name=\"video\" width=\"12\" height=\"12\" />\n          </el-button>\n          <el-button @click=\"toggleAudioSelection()\">\n            <mo-icon name=\"audio\" width=\"12\" height=\"12\" />\n          </el-button>\n          <el-button @click=\"toggleImageSelection()\">\n            <mo-icon name=\"image\" width=\"12\" height=\"12\" />\n          </el-button>\n          <el-button @click=\"toggleDocumentSelection()\">\n            <mo-icon name=\"document\" width=\"12\" height=\"12\" />\n          </el-button>\n        </el-button-group>\n      </el-col>\n      <el-col\n        class=\"files-summary\"\n        :xs=\"24\"\n        :sm=\"16\"\n        :md=\"16\"\n        :lg=\"16\"\n      >\n        {{ $t('task.selected-files-sum', { selectedFilesCount, selectedFilesTotalSize }) }}\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script>\n  import { isEmpty } from 'lodash'\n  import '@/components/Icons/video'\n  import '@/components/Icons/audio'\n  import '@/components/Icons/image'\n  import '@/components/Icons/document'\n  import {\n    NONE_SELECTED_FILES,\n    SELECTED_ALL_FILES\n  } from '@shared/constants'\n  import {\n    bytesToSize,\n    calcProgress,\n    filterAudioFiles,\n    filterDocumentFiles,\n    filterImageFiles,\n    filterVideoFiles,\n    removeExtensionDot\n  } from '@shared/utils'\n\n  export default {\n    name: 'mo-task-files',\n    filters: {\n      bytesToSize,\n      removeExtensionDot\n    },\n    props: {\n      mode: {\n        type: String,\n        default: 'ADD',\n        validator: function (value) {\n          return ['ADD', 'DETAIL'].indexOf(value) !== -1\n        }\n      },\n      height: {\n        type: [Number, String]\n      },\n      files: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      }\n    },\n    data () {\n      return {\n        selectedFiles: []\n      }\n    },\n    computed: {\n      selectedFilesCount () {\n        return this.selectedFiles.length\n      },\n      selectedFilesTotalSize () {\n        const result = this.selectedFiles.reduce((acc, cur) => {\n          return acc + parseInt(cur.length, 10)\n        }, 0)\n        return bytesToSize(result)\n      },\n      selectedFileIndex () {\n        const { files, selectedFiles } = this\n        if (files.length === 0 || selectedFiles.length === 0) {\n          return NONE_SELECTED_FILES\n        }\n        if (files.length === selectedFiles.length) {\n          return SELECTED_ALL_FILES\n        }\n        const indexArr = this.selectedFiles.map((item) => item.idx)\n        const result = indexArr.join(',')\n        return result\n      }\n    },\n    watch: {\n      selectedFileIndex () {\n        const { selectedFileIndex } = this\n        this.$emit('selection-change', selectedFileIndex)\n      }\n    },\n    methods: {\n      calcProgress,\n      toggleAllSelection () {\n        if (!this.$refs.torrentTable) {\n          return\n        }\n        this.$refs.torrentTable.toggleAllSelection()\n      },\n      clearSelection () {\n        if (!this.$refs.torrentTable) {\n          return\n        }\n        this.$refs.torrentTable.clearSelection()\n      },\n      toggleSelection (rows) {\n        if (isEmpty(rows)) {\n          this.$refs.torrentTable.clearSelection()\n        } else {\n          this.$refs.torrentTable.clearSelection()\n          rows.forEach(row => {\n            this.$refs.torrentTable.toggleRowSelection(row, true)\n          })\n        }\n      },\n      toggleVideoSelection () {\n        const filtered = filterVideoFiles(this.files)\n        this.toggleSelection(filtered)\n      },\n      toggleAudioSelection () {\n        const filtered = filterAudioFiles(this.files)\n        this.toggleSelection(filtered)\n      },\n      toggleImageSelection () {\n        const filtered = filterImageFiles(this.files)\n        this.toggleSelection(filtered)\n      },\n      toggleDocumentSelection () {\n        const filtered = filterDocumentFiles(this.files)\n        this.toggleSelection(filtered)\n      },\n      handleRowDbClick (row, column, event) {\n        this.$refs.torrentTable.toggleRowSelection(row)\n      },\n      handleSelectionChange (val) {\n        this.selectedFiles = val\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.file-filters {\n  margin-top: 0.5rem;\n  .quick-filters {\n    button {\n      font-size: 0;\n    }\n  }\n  .files-summary {\n    text-align: right;\n    font-size: $--font-size-base;\n    color: $--color-text-regular;\n    line-height: 1.75rem;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/TaskGeneral.vue",
    "content": "<template>\n  <el-form\n    class=\"mo-task-general\"\n    ref=\"form\"\n    :model=\"form\"\n    :label-width=\"formLabelWidth\"\n    v-if=\"task\"\n  >\n    <el-form-item :label=\"`${$t('task.task-gid')}: `\">\n      <div class=\"form-static-value\">\n        {{ task.gid }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-name')}: `\">\n      <div class=\"form-static-value\">\n        {{ taskFullName }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-dir')}: `\">\n      <el-input placeholder=\"\" readonly v-model=\"path\">\n        <mo-show-in-folder\n          slot=\"append\"\n          v-if=\"isRenderer\"\n          :path=\"path\"\n        />\n      </el-input>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-status')}: `\">\n      <div class=\"form-static-value\">\n        <mo-task-status :theme=\"currentTheme\" :status=\"taskStatus\" />\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-error-info')}: `\" v-if=\"task.errorCode && task.errorCode !== '0'\">\n      <div class=\"form-static-value\">\n        {{ task.errorCode }} {{ task.errorMessage }}\n      </div>\n    </el-form-item>\n\n    <el-divider v-if=\"isBT\">\n      <i class=\"el-icon-attract\"></i>\n      {{ $t('task.task-bittorrent-info') }}\n    </el-divider>\n\n    <el-form-item :label=\"`${$t('task.task-info-hash')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.infoHash }}\n        <i class=\"copy-link\" @click=\"handleCopyClick\">\n          <mo-icon\n            name=\"link\"\n            width=\"12\"\n            height=\"12\"\n          />\n        </i>\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-piece-length')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.pieceLength | bytesToSize }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-num-pieces')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.numPieces }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-bittorrent-creation-date')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.bittorrent.creationDate | localeDateTimeFormat(locale) }}\n      </div>\n    </el-form-item>\n    <el-form-item :label=\"`${$t('task.task-bittorrent-comment')}: `\" v-if=\"isBT\">\n      <div class=\"form-static-value\">\n        {{ task.bittorrent.comment }}\n      </div>\n    </el-form-item>\n  </el-form>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapState } from 'vuex'\n  import {\n    bytesToSize,\n    calcFormLabelWidth,\n    checkTaskIsBT,\n    checkTaskIsSeeder,\n    getTaskName,\n    getTaskUri,\n    localeDateTimeFormat\n  } from '@shared/utils'\n  import { APP_THEME, TASK_STATUS } from '@shared/constants'\n  import { getTaskFullPath } from '@/utils/native'\n  import ShowInFolder from '@/components/Native/ShowInFolder'\n  import TaskStatus from '@/components/Task/TaskStatus'\n  import '@/components/Icons/folder'\n  import '@/components/Icons/link'\n\n  export default {\n    name: 'mo-task-general',\n    components: {\n      [ShowInFolder.name]: ShowInFolder,\n      [TaskStatus.name]: TaskStatus\n    },\n    props: {\n      task: {\n        type: Object\n      }\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      return {\n        form: {},\n        formLabelWidth: calcFormLabelWidth(locale),\n        locale\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      ...mapState('app', {\n        systemTheme: state => state.systemTheme\n      }),\n      ...mapState('preference', {\n        theme: state => state.config.theme\n      }),\n      currentTheme () {\n        if (this.theme === APP_THEME.AUTO) {\n          return this.systemTheme\n        } else {\n          return this.theme\n        }\n      },\n      taskFullName () {\n        return getTaskName(this.task, {\n          defaultName: this.$t('task.get-task-name'),\n          maxLen: -1\n        })\n      },\n      taskName () {\n        return getTaskName(this.task, {\n          defaultName: this.$t('task.get-task-name'),\n          maxLen: 32\n        })\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      taskStatus () {\n        const { task, isSeeder } = this\n        if (isSeeder) {\n          return TASK_STATUS.SEEDING\n        } else {\n          return task.status\n        }\n      },\n      path () {\n        return getTaskFullPath(this.task)\n      },\n      isBT () {\n        return checkTaskIsBT(this.task)\n      }\n    },\n    filters: {\n      bytesToSize,\n      localeDateTimeFormat\n    },\n    methods: {\n      handleCopyClick () {\n        const { task } = this\n        const uri = getTaskUri(task)\n        navigator.clipboard.writeText(uri)\n          .then(() => {\n            this.$msg.success(this.$t('task.copy-link-success'))\n          })\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.copy-link {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/TaskPeers.vue",
    "content": "<template>\n  <div class=\"mo-task-peers\">\n    <div class=\"mo-table-wrapper\">\n      <el-table\n        stripe\n        ref=\"peerTable\"\n        class=\"mo-peer-table\"\n        :data=\"peers\"\n      >\n        <el-table-column\n          :label=\"`${$t('task.task-peer-host')}: `\"\n          min-width=\"140\">\n          <template slot-scope=\"scope\">\n            {{ `${scope.row.ip}:${scope.row.port}` }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          :label=\"`${$t('task.task-peer-client')}: `\"\n          min-width=\"125\">\n          <template slot-scope=\"scope\">\n            {{ scope.row.peerId | peerIdParser }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          :label=\"`%`\"\n          align=\"right\"\n          width=\"55\">\n          <template slot-scope=\"scope\">\n            {{ scope.row.bitfield | bitfieldToPercent }}%\n          </template>\n        </el-table-column>\n        <el-table-column\n          :label=\"`↑`\"\n          align=\"right\"\n          width=\"90\">\n          <template slot-scope=\"scope\">\n            {{ scope.row.uploadSpeed | bytesToSize }}/s\n          </template>\n        </el-table-column>\n        <el-table-column\n          :label=\"`↓`\"\n          align=\"right\"\n          width=\"90\">\n          <template slot-scope=\"scope\">\n            {{ scope.row.downloadSpeed | bytesToSize }}/s\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n  </div>\n</template>\n\n<script>\n  import {\n    bitfieldToPercent,\n    bytesToSize,\n    peerIdParser\n  } from '@shared/utils'\n\n  export default {\n    name: 'mo-task-peers',\n    filters: {\n      bitfieldToPercent,\n      bytesToSize,\n      peerIdParser\n    },\n    props: {\n      peers: {\n        type: Array,\n        default: function () {\n          return []\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.el-table.mo-peer-table .cell {\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskDetail/TaskTrackers.vue",
    "content": "<template>\n  <el-form\n    ref=\"form\"\n    :model=\"form\"\n    :label-width=\"formLabelWidth\"\n    v-if=\"task\"\n  >\n    <div\n      class=\"tracker-list\"\n      v-if=\"announceList\"\n    >\n      <el-input\n        readonly\n        autosize\n        type=\"textarea\"\n        auto-complete=\"off\"\n        v-model=\"announceList\">\n      </el-input>\n    </div>\n  </el-form>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import {\n    calcFormLabelWidth,\n    checkTaskIsBT,\n    checkTaskIsSeeder\n  } from '@shared/utils'\n  import { convertTrackerDataToLine } from '@shared/utils/tracker'\n  import { EMPTY_STRING } from '@shared/constants'\n\n  export default {\n    name: 'mo-task-trackers',\n    props: {\n      task: {\n        type: Object\n      }\n    },\n    data () {\n      const { locale } = this.$store.state.preference.config\n      return {\n        form: {},\n        formLabelWidth: calcFormLabelWidth(locale),\n        locale\n      }\n    },\n    computed: {\n      isRenderer: () => is.renderer(),\n      isBT () {\n        return checkTaskIsBT(this.task)\n      },\n      isSeeder () {\n        return checkTaskIsSeeder(this.task)\n      },\n      announceList () {\n        if (!this.isBT) {\n          return EMPTY_STRING\n        }\n\n        const { bittorrent } = this.task\n        const data = bittorrent.announceList.map((i) => i[0])\n        return convertTrackerDataToLine(data)\n      }\n    },\n    methods: {\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.tracker-list {\n  padding: 0;\n  margin: 0;\n  font-size: $--font-size-small;\n  textarea {\n    line-height: 2;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskGraphic/Atom.vue",
    "content": "<template>\n  <rect\n    :class=\"klass\"\n    :status=\"status\"\n    :width=\"width\"\n    :height=\"height\"\n    :rx=\"radius\"\n    :ry=\"radius\"\n    :x=\"x\"\n    :y=\"y\"\n    >\n  </rect>\n</template>\n\n<script>\n  export default {\n    name: 'mo-task-graphic-atom',\n    props: {\n      status: {\n        type: Number\n      },\n      width: {\n        type: Number,\n        default: 10\n      },\n      height: {\n        type: Number,\n        default: 10\n      },\n      radius: {\n        type: Number,\n        default: 2\n      },\n      x: {\n        type: Number\n      },\n      y: {\n        type: Number\n      }\n    },\n    computed: {\n      klass () {\n        const { status } = this\n        return `graphic-atom graphic-atom-s${status}`\n      }\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n.graphic-atom {\n  shape-rendering: geometricPrecision;\n  outline-offset: -1px;\n}\n.graphic-atom-s0 {\n  fill: $--graphic-atom-color-0;\n  outline: 1px solid $--graphic-atom-outline-color;\n}\n.graphic-atom-s1 {\n  fill: $--graphic-atom-color-1;\n  outline: 1px solid $--graphic-atom-outline-color;\n}\n.graphic-atom-s2 {\n  fill: $--graphic-atom-color-2;\n  outline: 1px solid $--graphic-atom-outline-color;\n}\n.graphic-atom-s3 {\n  fill: $--graphic-atom-color-3;\n  outline: 1px solid $--graphic-atom-outline-color;\n}\n.graphic-atom-s4 {\n  fill: $--graphic-atom-color-4;\n  outline: 1px solid $--graphic-atom-outline-color;\n}\n</style>\n"
  },
  {
    "path": "src/renderer/components/TaskGraphic/Index.vue",
    "content": "<template>\n  <svg version=\"1.1\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    class=\"svg-task-graphic\"\n    :width=\"width\"\n    :height=\"height\"\n    :viewBox=\"box\">\n    <g v-for=\"(row, index) in atoms\" :key=\"`g-${index}`\" >\n      <mo-task-graphic-atom\n        v-for=\"atom in row\"\n        :key=\"`atom-${atom.id}`\"\n        :status=\"atom.status\"\n        :width=\"atomWidth\"\n        :height=\"atomHeight\"\n        :radius=\"atomRadius\"\n        :x=\"atom.x\"\n        :y=\"atom.y\"\n      />\n    </g>\n  </svg>\n</template>\n\n<script>\n  import Atom from './Atom'\n\n  export default {\n    name: 'mo-task-graphic',\n    components: {\n      [Atom.name]: Atom\n    },\n    props: {\n      bitfield: {\n        type: String,\n        default: ''\n      },\n      outerWidth: {\n        type: Number,\n        default: 240\n      },\n      atomWidth: {\n        type: Number,\n        default: 10\n      },\n      atomHeight: {\n        type: Number,\n        default: 10\n      },\n      atomGutter: {\n        type: Number,\n        default: 3\n      },\n      atomRadius: {\n        type: Number,\n        default: 2\n      }\n    },\n    computed: {\n      len () {\n        return this.bitfield.length\n      },\n      atomWG () {\n        return this.atomWidth + this.atomGutter\n      },\n      atomHG () {\n        return this.atomHeight + this.atomGutter\n      },\n      columnCount () {\n        const { outerWidth, atomWidth, atomWG } = this\n        const result = parseInt((outerWidth - atomWidth) / atomWG, 10) + 1\n        return result\n      },\n      rowCount () {\n        const { len, columnCount } = this\n        const result = parseInt((len / columnCount), 10) + 1\n        return result\n      },\n      offset () {\n        const { outerWidth, atomWidth, atomWG, columnCount } = this\n        const totalWidth = atomWG * (columnCount - 1) + atomWidth\n        const result = (outerWidth - totalWidth) / 2\n        return parseFloat(result.toFixed(2))\n      },\n      width () {\n        const { atomWidth, atomWG, columnCount } = this\n        const result = atomWG * (columnCount - 1) + atomWidth\n        return parseInt(result, 10)\n      },\n      height () {\n        const { atomHeight, atomHG, rowCount, offset } = this\n        const result = atomHG * (rowCount - 1) + atomHeight + offset * 2\n        return parseInt(result, 10)\n      },\n      box () {\n        return `0 0 ${this.width} ${this.height}`\n      },\n      atoms () {\n        const { len, columnCount } = this\n        const result = []\n        let row = []\n        for (let i = 0; i < len; i++) {\n          row.push(this.buildAtom(i))\n\n          if ((i + 1) % columnCount === 0) {\n            result.push(row)\n            row = []\n          }\n        }\n        result.push(row)\n\n        return result\n      }\n    },\n    methods: {\n      buildAtom (index) {\n        const { bitfield, offset, atomWG, atomHG, columnCount } = this\n        const hIndex = index + 1\n        let chIndex = index % columnCount\n        let rhIndex = parseInt((index / columnCount), 10)\n        chIndex = chIndex < 0 ? 0 : chIndex\n        rhIndex = rhIndex < 0 ? 0 : rhIndex\n        const result = {\n          id: `${hIndex}`,\n          status: Math.floor(parseInt(bitfield[index], 16) / 4),\n          x: chIndex * atomWG,\n          y: offset + rhIndex * atomHG\n        }\n\n        return result\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/renderer/components/Theme/Dark/Variables.scss",
    "content": "/* Color\n-------------------------- */\n$--dk-border-color-base: #555;\n$--dk-font-color-base: #eee;\n$--dk-action-color-base: #eee;\n\n/* App\n-------------------------- */\n$--dk-app-background: transparent !default;\n$--dk-titlebar-actions-color: $--dk-action-color-base !default;\n$--dk-titlebar-close-active-color: #fff !default;\n$--dk-titlebar-actions-active-background: rgba(0, 0, 0, 0.9) !default;\n$--dk-titlebar-close-active-background: #fd0007 !default;\n$--dk-app-logo-color: #eee !default;\n$--dk-app-version-color: #a2a3a4 !default;\n$--dk-app-engine-title-color: #a2a3a4 !default;\n$--dk-app-engine-info-color: #777 !default;\n$--dk-app-copyright-color: #a2a3a4 !default;\n\n/* Aside\n-------------------------- */\n$--dk-aside-background: rgba(0, 0, 0, 0.9) !default;\n$--dk-aside-text-color: #fff !default;\n\n/* SubNav\n-------------------------- */\n$--dk-subnav-background: #2D2D2D !default;\n$--dk-subnav-title-color: #fff !default;\n$--dk-subnav-action-color: #fff !default;\n$--dk-subnav-text-color: #aaa !default;\n$--dk-subnav-active-text-color: #fff !default;\n$--dk-subnav-active-background: #444 !default;\n$--dk-subnav-border-color: #ccc !default;\n\n/* Main\n-------------------------- */\n$--dk-main-background: #343434 !default;\n\n/* Panel\n-------------------------- */\n$--dk-panel-background: #343434 !default;\n$--dk-panel-title-color: #fff !default;\n$--dk-panel-border-color: #EBECF0 !default;\n\n/* Form Actions\n-------------------------- */\n$--dk-form-actions-background: #343434 !default;\n\n/* Task\n-------------------------- */\n$--dk-task-action-color: $--dk-action-color-base !default;\n$--dk-task-action-hover-color: $--color-primary !default;\n$--dk-task-item-backgroud: #2d2d2d !default;\n$--dk-task-item-border-color: $--dk-border-color-base !default;\n$--dk-task-item-hover-border-color: $--color-primary !default;\n$--dk-task-item-hover-background: $--color-primary !default;\n$--dk-task-item-text-color: #eee !default;\n$--dk-task-item-action-background: #4a4a4a !default;\n$--dk-task-item-action-hover-background: $--color-primary !default;\n$--dk-task-item-action-border-color: #5f5f5f !default;\n$--dk-task-item-action-hover-border-color: $--color-primary !default;\n$--dk-task-item-action-color: $--dk-action-color-base !default;\n$--dk-task-item-action-hover-color: #fff !default;\n$--dk-no-task-color: #aaa !default;\n$--dk-add-task-dialog-footer-background: #4a4a4a !default;\n$--dk-task-detail-box-border: #565656 !default;\n\n/* Preference\n-------------------------- */\n$--dk-preference-form-text-color: #dfdfdf !default;\n\n/* Speedometer\n-------------------------- */\n$--dk-speedometer-background: #333 !default;\n$--dk-speedometer-border-color: #5f5f5f !default;\n$--dk-speedometer-hover-border-color: #9b9b9b !default;\n$--dk-speedometer-primary-color: $--color-primary !default;\n$--dk-speedometer-stopped-color: #9b9b9b !default;\n$--dk-speedometer-text-color: #9b9b9b !default;\n\n/* Task Graphic\n-------------------------- */\n$--dk-graphic-box-background: #3f3f3f !default;\n$--dk-graphic-atom-color-0: #161b22 !default;\n$--dk-graphic-atom-color-1: #0e4429 !default;\n$--dk-graphic-atom-color-2: #006d32 !default;\n$--dk-graphic-atom-color-3: #26a641 !default;\n$--dk-graphic-atom-color-4: #39d353 !default;\n\n/* Element UI\n-------------------------- */\n$--dk-dialog-background: #343434 !default;\n$--dk-dialog-text-color: #9b9b9b !default;\n$--dk-table-background: #2a2b2c !default;\n$--dk-table-striped-background: #1f2122 !default;\n$--dk-table-hover-background: #565656 !default;\n$--dk-table-th-background: #1f2122 !default;\n$--dk-table-text-color: #9b9b9b !default;\n$--dk-table-border-color: #565656 !default;\n$--dk-popover-background: #3d3d3d !default;\n$--dk-popover-border-color: $--dk-border-color-base !default;\n"
  },
  {
    "path": "src/renderer/components/Theme/Dark.scss",
    "content": ".theme-dark {\n  .title-bar .window-actions > li {\n    color: $--dk-titlebar-actions-color;\n    &:hover {\n      background-color: $--dk-titlebar-actions-active-background;\n    }\n    &.win-close-btn:hover {\n      background-color: $--dk-titlebar-close-active-background;\n    }\n  }\n\n  .logo > a {\n    color: $--dk-app-logo-color;\n  }\n\n  .app-info .app-version span {\n    color: $--dk-app-version-color;\n  }\n\n  .app-info .engine-info {\n    h4 {\n      color: $--dk-app-engine-title-color;\n    }\n    ul {\n      color: $--dk-app-engine-info-color;\n    }\n  }\n\n  .copyright a {\n    color: $--dk-app-copyright-color;\n  }\n\n  .aside {\n    background-color: $--dk-aside-background;\n  }\n\n  .subnav {\n    background-color: $--dk-subnav-background;\n    color: $--dk-subnav-text-color;\n  }\n\n  .subnav-inner {\n    h3 {\n      color: $--dk-subnav-title-color;\n    }\n    ul li {\n      &:hover,\n      &.active {\n        background-color: $--dk-subnav-active-background;\n\n        i,\n        span,\n        svg {\n          color: $--dk-subnav-active-text-color;\n        }\n      }\n    }\n  }\n\n  .form-actions {\n    background: $--dk-form-actions-background;\n  }\n\n  .main {\n    background-color: $--dk-main-background;\n  }\n\n  .panel {\n    background-color: $--dk-panel-background;\n  }\n\n  .panel .panel-header {\n    border-bottom-color: rgba(255, 255, 255, 0.1);\n    h4 {\n      color: $--dk-panel-title-color;\n    }\n  }\n\n  .task-item {\n    background-color: $--dk-task-item-backgroud;\n    border-color: $--dk-task-item-border-color;\n    &:hover {\n      border-color: $--dk-task-item-hover-border-color;\n    }\n  }\n\n  .selected .task-item {\n    border-color: $--dk-task-item-hover-border-color;\n  }\n\n  .task-name {\n    color: $--dk-task-item-text-color;\n  }\n\n  .task-actions {\n    color: $--dk-task-action-color;\n  }\n\n  .task-item-actions {\n    background-color: $--dk-task-item-action-background;\n    border-color: $--dk-task-item-action-border-color;\n    &:hover {\n      border-color: $--dk-task-item-action-hover-border-color;\n      color: $--dk-task-item-action-hover-color;\n      background-color: $--dk-task-item-action-hover-background;\n    }\n  }\n\n  .mo-speedometer {\n    background-color: $--dk-speedometer-background;\n    border-color: $--dk-speedometer-border-color;\n  }\n\n  .no-task {\n    color: $--dk-no-task-color;\n  }\n\n  .mo-table-wrapper {\n    border-color: $--dk-table-border-color;\n  }\n\n  .graphic-box {\n    border-color: $--dk-task-detail-box-border;\n    background-color: $--dk-graphic-box-background;\n  }\n\n  .graphic-atom-s0 {\n    fill: $--dk-graphic-atom-color-0;\n  }\n\n  .graphic-atom-s1 {\n    fill: $--dk-graphic-atom-color-1;\n  }\n\n  .graphic-atom-s2 {\n    fill: $--dk-graphic-atom-color-2;\n  }\n\n  .graphic-atom-s3 {\n    fill: $--dk-graphic-atom-color-3;\n  }\n\n  .graphic-atom-s4 {\n    fill: $--dk-graphic-atom-color-4;\n  }\n\n  .form-static-value {\n    color: #e7e7e7;\n  }\n\n  /* Element UI\n  -------------------------- */\n  .el-progress-bar__outer {\n    background-color: #4a4a4a;\n  }\n\n  .el-input__inner,\n  .el-textarea__inner {\n    background-color: #373737;\n    border-color: #5f5f5f;\n    color: #eee;\n    &:focus {\n      outline: none;\n      border-color: $--color-primary;\n    }\n    &::placeholder {\n      color: #777;\n    }\n  }\n\n  .el-input.is-disabled .el-input__inner {\n    background-color: #373737;\n    border-color: #5f5f5f;\n    color: #aaa;\n  }\n\n  .el-input-group__append,\n  .el-input-group__prepend {\n    background-color: #333;\n    border-color: #5f5f5f;\n    color: #e7e7e7;\n  }\n\n  .el-input-number__increase,\n  .el-input-number__decrease {\n    background-color: #333;\n    color: #e7e7e7;\n  }\n\n  .el-input-number__decrease {\n    border-right-color: #5f5f5f;\n  }\n\n  .el-input-number__increase {\n    border-left-color: #5f5f5f;\n  }\n\n  .el-input-number.is-controls-right .el-input-number__increase {\n    border-left-color: #5f5f5f;\n    border-bottom-color: #5f5f5f;\n  }\n\n  .el-input-number.is-controls-right .el-input-number__decrease {\n    border-left-color: #5f5f5f;\n  }\n\n  .el-form-item__label,\n  .el-checkbox,\n  .el-radio {\n    color: $--dk-preference-form-text-color;\n  }\n\n  .el-switch__core,\n  .el-checkbox__inner {\n    border-color: #606060;\n    background-color: #5c5d5f;\n  }\n\n  .el-select .el-input .el-select__caret {\n    color: #e7e7e7;\n  }\n\n  .el-select-dropdown {\n    background-color: #3d3d3d;\n    border-color: #606060;\n  }\n\n  .el-select-dropdown__item {\n    color: #eee;\n    &.selected {\n      color: $--color-primary;\n    }\n    &.hover,\n    &:hover {\n      background-color: $--color-primary;\n      color: #fff;\n    }\n  }\n\n  .el-select-dropdown.is-multiple .el-select-dropdown__item.selected {\n    background-color: #3d3d3d;\n    color: $--color-primary;\n  }\n\n  .el-upload-dragger {\n    background-color: #2d2d2d;\n    border-color: #606060;\n    > i,\n    .el-upload__text {\n      color: #a2a3a4;\n    }\n  }\n\n  .el-popper[x-placement^=\"top\"] .popper__arrow {\n    border-top-color: #606060;\n    &:after {\n      border-top-color: #3d3d3d;\n    }\n  }\n\n  .el-popper[x-placement^=\"right\"] .popper__arrow {\n    border-right-color: #606060;\n    &:after {\n      border-right-color: #3d3d3d;\n    }\n  }\n\n  .el-popper[x-placement^=\"bottom\"] .popper__arrow {\n    border-bottom-color: #606060;\n    &:after {\n      border-bottom-color: #3d3d3d;\n    }\n  }\n\n  .el-popper[x-placement^=\"left\"] .popper__arrow {\n    border-left-color: #606060;\n    &:after {\n      border-left-color: #3d3d3d;\n    }\n  }\n\n  .el-button {\n    background-color: #5b5b5b;\n    border-color: #606060;\n    color: #e6e6e6;\n    &:hover,\n    &:focus {\n      color: $--color-primary-light-4;\n      border-color: #606060;\n      background-color: #333;\n    }\n  }\n\n  .el-button--primary {\n    color: #eee;\n    background-color: $--color-primary;\n    border-color: $--color-primary;\n    &:hover,\n    &:focus {\n      background: $--color-primary;\n      border-color: $--color-primary;\n      color: #fff;\n    }\n  }\n\n  .el-button--danger.is-plain {\n    color: #ff6157;\n    background-color: #5b5b5b;\n    border-color: #ffc0bc;\n  }\n\n  .el-button--danger.is-plain:hover,\n  .el-button--danger.is-plain:focus {\n    background-color: #ff6157;\n    border-color: #ff6157;\n    color: #fff;\n  }\n\n  /* Message */\n  .el-message {\n    background-color: #2d2d2d;\n    border-color: #606060;\n  }\n\n  .el-message__closeBtn {\n    color: rgba(255, 255, 255, 0.3);\n  }\n\n  .el-message--info .el-message__content {\n    color: #a2a3a4;\n  }\n\n  .el-message--success {\n    background-color: #2d2d2d;\n    border-color: #606060;\n    .el-message__content {\n      color: #67c23a;\n    }\n  }\n\n  .el-message--warning {\n    background-color: #2d2d2d;\n    border-color: #606060;\n    .el-message__content {\n      color: #e6a23c;\n    }\n  }\n\n  .el-message--error {\n    background-color: #2d2d2d;\n    border-color: #606060;\n    .el-message__content {\n      color: #f56c6c;\n    }\n  }\n\n  /* Dialog */\n  .el-dialog {\n    background-color: $--dk-dialog-background;\n    .el-dialog__body {\n      color: $--dk-dialog-text-color;\n    }\n  }\n\n  .add-task-dialog .el-dialog__footer {\n    background-color: $--dk-add-task-dialog-footer-background;\n  }\n\n  .torrent-file-list {\n    border-color: $--dk-table-border-color;\n  }\n\n  .el-table {\n    background-color: $--dk-table-background;\n    color: $--dk-table-text-color;\n    tr {\n      background-color: $--dk-table-background;\n    }\n    th {\n      background-color: $--dk-table-th-background;\n      color: $--dk-table-text-color;\n    }\n    th.is-leaf, td {\n      background-color: $--dk-table-background;\n      color: $--dk-table-text-color;\n      border-bottom-color: $--dk-table-border-color;\n    }\n  }\n  .el-table thead th.is-leaf {\n    background-color: $--dk-table-th-background;\n  }\n\n  .el-table--enable-row-hover .el-table__body tr:hover > td,\n  .el-table--enable-row-hover .el-table__body tr.el-table__row--striped:hover > td {\n    background-color: $--dk-table-hover-background;\n  }\n\n  .el-table--group::after, .el-table--border::after, .el-table::before {\n    background-color: $--dk-table-border-color;\n  }\n\n  .el-table--striped .el-table__body tr.el-table__row--striped td {\n    background-color: $--dk-table-striped-background;\n  }\n\n  /* Tabs */\n  .el-tabs__item {\n    color: #a2a3a4;\n    &.is-active {\n      color: $--color-primary;\n    }\n  }\n\n  .el-tabs__nav-wrap::after {\n    background-color: #606060;\n  }\n\n  .form-preference .el-form-item__content {\n    color: $--dk-preference-form-text-color;\n  }\n\n  .form-preference .el-switch__label {\n    color: $--dk-preference-form-text-color;\n  }\n\n  .form-preference .el-checkbox__input.is-checked + .el-checkbox__label {\n    color: $--dk-preference-form-text-color;\n  }\n\n  .form-preference .el-form-item {\n    a {\n      color: #dfdfdf;\n      &:hover {\n        color: #eee;\n      }\n      &:active {\n        color: #eee;\n      }\n    }\n  }\n\n  /* Divider */\n  .el-divider {\n    background-color: #666;\n  }\n  .el-divider__text {\n    background-color: $--dk-panel-background;\n    color: #a7a7a7;\n  }\n\n  /* Popover */\n  .el-popover {\n    background-color: $--dk-popover-background;\n    border-color: $--dk-popover-border-color;\n  }\n\n  .el-tag.el-tag--info.el-tag--light {\n    background-color: #5b5b5b;\n    border-color: #606060;\n    color: #e6e6e6;\n  }\n\n  .el-tag__close.el-icon-close {\n    color: #2d2d2d;\n  }\n}\n"
  },
  {
    "path": "src/renderer/components/Theme/Default.scss",
    "content": "html,\nbody {\n  height: 100%;\n  padding: 0;\n}\n\nbody {\n  font-family: \"Monospaced Number\", \"Chinese Quote\", -apple-system,\n    BlinkMacSystemFont, \"Segoe UI\", Roboto, \"PingFang SC\", \"Hiragino Sans GB\",\n    \"Microsoft YaHei\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-variant: tabular-nums;\n  font-size: $--font-size-medium;\n}\n\n::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n::-webkit-scrollbar-thumb {\n  border-radius: 8px;\n  background-color: rgba(0, 0, 0, 0.4);\n}\n\n::-webkit-scrollbar-thumb:window-inactive {\n  background-color: rgba(0, 0, 0, 0.25);\n}\n\n::-webkit-scrollbar-corner {\n  background: transparent;\n}\n\n::-webkit-resizer {\n  display: none;\n}\n\nimg {\n  width: auto;\n  height: auto;\n  max-width: 100%;\n  max-height: 100%;\n}\n\niframe {\n  border: 0;\n}\n\n.clearfix {\n  @include clearfix();\n}\n\n/* Element UI\n-------------------------- */\n.el-progress-bar__inner {\n  transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;\n}\n\n.el-progress--line.is-text {\n  .el-progress-bar__inner::before {\n    content: \"\";\n    opacity: 0;\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background: #fff;\n    border-radius: 10px;\n    animation: mo-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite;\n  }\n}\n\n@keyframes mo-progress-active {\n  0% {\n    opacity: 0.1;\n    width: 0;\n  }\n  20% {\n    opacity: 0.5;\n    width: 0;\n  }\n  100% {\n    opacity: 0;\n    width: 100%;\n  }\n}\n\n.el-message-box__wrapper {\n  outline: none;\n}\n\n.el-message__content {\n  line-height: 18px;\n  word-break: break-all;\n  a {\n    color: $--link-color;\n  }\n}\n\n.tab-title-dialog {\n  .el-dialog__header {\n    padding: 0 20px;\n    position: relative;\n    z-index: 10;\n  }\n  .el-dialog__body {\n    padding-top: 10px;\n    padding-bottom: 10px;\n  }\n}\n\n.el-form-item--mini .el-form-item__info {\n  padding-top: 1px;\n}\n\n.el-form-item__info {\n  font-size: 12px;\n  line-height: 1;\n  padding-top: 4px;\n  color: $--color-info;\n  > a {\n    margin-right: 10px;\n  }\n}\n\n.el-input-number.el-input-number--mini {\n  &.is-controls-left .el-input__inner {\n    padding-left: 34px;\n    padding-right: 5px;\n  }\n  &.is-controls-right .el-input__inner {\n    padding-left: 5px;\n    padding-right: 34px;\n  }\n}\n\n.el-drawer__container {\n  outline: none;\n}\n\n/* App Main\n-------------------------- */\n#app,\n#container {\n  height: 100%;\n  background-color: $--app-background;\n}\n\n.draggable {\n  -webkit-app-region: drag;\n  user-select: none;\n}\n\n.non-draggable {\n  -webkit-app-region: no-drag;\n}\n\n.aside {\n  background-color: $--aside-background;\n  color: $--aside-text-color;\n}\n\n.subnav {\n  background-color: $--subnav-background;\n  color: $--subnav-text-color;\n}\n\n.main {\n  background-color: $--main-background;\n}\n\n.subnav-inner {\n  margin-top: 44px;\n  padding: 0 16px;\n  user-select: none;\n\n  h3 {\n    font-size: 16px;\n    color: $--subnav-title-color;\n    font-weight: normal;\n    line-height: 24px;\n    margin: 0 0 28px;\n  }\n\n  ul {\n    list-style: none;\n    padding: 0;\n    margin: 0;\n    cursor: default;\n\n    li {\n      margin-bottom: 8px;\n      padding: 8px 10px;\n      font-size: 0;\n      line-height: 20px;\n      border-radius: 3px;\n      cursor: pointer;\n\n      i,\n      span {\n        display: inline-block;\n        vertical-align: middle;\n        font-size: 14px;\n      }\n\n      &:hover,\n      &.active {\n        background-color: $--subnav-active-background;\n\n        i,\n        span,\n        svg {\n          color: $--subnav-active-text-color;\n        }\n      }\n    }\n  }\n}\n\n.subnav-icon {\n  padding: 2px;\n  height: 16px;\n  margin-right: 12px;\n\n  svg {\n    width: 16px;\n    height: 16px;\n  }\n}\n\n.content {\n  padding: 0;\n}\n\n.panel {\n  background: $--panel-background;\n  .panel-header {\n    position: relative;\n    padding: 44px 0 12px;\n    margin: 0 16px;\n    border-bottom: 2px solid $--panel-border-color;\n    user-select: none;\n    h4 {\n      margin: 0;\n      color: $--panel-title-color;\n      font-size: 16px;\n      font-weight: normal;\n      line-height: 24px;\n    }\n  }\n  .panel-content {\n    position: relative;\n    padding: 0;\n    height: 100%;\n  }\n}\n\n.form-actions {\n  background: $--form-actions-background;\n}\n\n.mo-table-wrapper {\n  border: 1px solid $--table-border-color;\n  border-bottom: none;\n  overflow-x: hidden;\n  overflow-y: auto;\n  .el-table th {\n    padding: 2px 0;\n  }\n}\n\n.graphic-box {\n  padding: 0.5rem 0.375rem;\n  margin-bottom: 1.5rem;\n  font-size: 0;\n  line-height: 0;\n  border: 1px solid $--task-detail-box-border;\n  border-radius: $--border-radius-base;\n  &> svg {\n    display: block;\n    margin: 0 auto;\n  }\n}\n\n.form-static-value {\n  word-break: break-all;\n  color: $--input-font-color;\n}\n\n@media only screen and (max-width:567px) {\n  .hidden-xs-only {\n    display:none !important\n  }\n}\n\n@media only screen and (min-width: 568px) {\n  .panel {\n    .panel-header {\n      margin-left: 36px;\n      margin-right: 36px;\n    }\n  }\n  .task-list {\n    padding-left: 36px;\n    padding-right: 36px;\n  }\n  .form-preference {\n    padding-left: 36px;\n  }\n  .form-actions {\n    padding: 24px 36px;\n  }\n  .mo-speedometer {\n    right: 36px;\n  }\n  .hidden-sm-and-up {\n    display: none !important;\n  }\n}\n@media only screen and (min-width: 568px) and (max-width: 991px) {\n  .hidden-sm-only {\n    display: none !important;\n  }\n}\n\n@media only screen and (max-width: 791px) {\n  .hidden-sm-and-down {\n    display: none !important;\n  }\n}\n\n@media only screen and (min-width: 792px) {\n  .hidden-md-and-up {\n    display: none !important;\n  }\n}\n"
  },
  {
    "path": "src/renderer/components/Theme/Index.scss",
    "content": "/* Normalize.css\n-------------------------- */\n@import '~normalize.css/normalize.css';\n\n/* Element UI\n-------------------------- */\n@import '~element-ui/packages/theme-chalk/src/index';\n\n/* Theme Light (default)\n-------------------------- */\n@import './Default.scss';\n\n/* Theme Dark\n-------------------------- */\n@import './Dark.scss';\n"
  },
  {
    "path": "src/renderer/components/Theme/Light/Variables.scss",
    "content": "/* App\n-------------------------- */\n$--app-background: transparent !default;\n$--titlebar-actions-color: #1f1f1f !default;\n$--titlebar-actions-active-background: #eee !default;\n$--titlebar-close-active-color: #fff !default;\n$--titlebar-close-active-background: #fd0007 !default;\n$--app-logo-color: #4D515A !default;\n$--app-version-color: $--color-text-regular !default;\n$--app-engine-title-color: $--color-text-regular !default;\n$--app-engine-info-color: $--color-text-secondary !default;\n$--app-copyright-color: $--color-text-regular !default;\n\n/* Aside\n-------------------------- */\n$--aside-background: rgba(0, 0, 0, 0.8) !default;\n$--aside-text-color: #fff !default;\n\n/* SubNav\n-------------------------- */\n$--subnav-background: #F4F5F7 !default;\n$--subnav-title-color: $--color-text-primary !default;\n$--subnav-action-color: #4D515A !default;\n$--subnav-text-color: #4D515A !default;\n$--subnav-active-text-color: $--color-primary !default;\n$--subnav-active-background: #EAECF0 !default;\n$--subnav-border-color: #ccc !default;\n\n/* Main\n-------------------------- */\n$--main-background: $--color-white !default;\n\n/* Panel\n-------------------------- */\n$--panel-background: $--color-white !default;\n$--panel-title-color: $--color-text-primary !default;\n$--panel-border-color: rgba(0, 0, 0, 0.1) !default;\n\n/* Form Actions\n-------------------------- */\n$--form-actions-background: $--color-white !default;\n\n/* Task\n-------------------------- */\n$--task-action-color: #4d515a !default;\n$--task-action-hover-color: $--color-primary !default;\n$--task-action-disabled-color: rgba(77, 81, 90, 0.5) !default;\n$--task-item-background: #fff !default;\n$--task-item-border-color: #ccc !default;\n$--task-item-hover-border-color: $--color-primary !default;\n$--task-item-hover-background: $--color-primary !default;\n$--task-item-text-color: #505753 !default;\n$--task-item-action-background: #fff !default;\n$--task-item-action-hover-background: $--color-primary !default;\n$--task-item-action-border-color: #F5F5F5 !default;\n$--task-item-action-hover-border-color: $--color-primary !default;\n$--task-item-action-color: #9B9B9B !default;\n$--task-item-action-hover-color: #fff !default;\n$--no-task-color: #eee !default;\n$--add-task-dialog-footer-background: #f5f5f5 !default;\n$--task-detail-box-border: #ebeef5 !default;\n\n/* Preference\n-------------------------- */\n$--preference-form-text-color: #4c4c4c;\n\n/* Speedometer\n-------------------------- */\n$--speedometer-background: $--color-white !default;\n$--speedometer-border-color: #ccc !default;\n$--speedometer-hover-border-color: #9b9b9b !default;\n$--speedometer-primary-color: $--color-primary !default;\n$--speedometer-stopped-color: #9b9b9b !default;\n$--speedometer-text-color: #9b9b9b !default;\n\n/* Task Graphic\n-------------------------- */\n$--graphic-box-background: transparent !default;\n$--graphic-atom-outline-color: rgba(27, 31, 35, 0.06) !default;\n$--graphic-atom-color-0: #ebedf0 !default;\n$--graphic-atom-color-1: #9be9a8 !default;\n$--graphic-atom-color-2: #40c463 !default;\n$--graphic-atom-color-3: #30a14e !default;\n$--graphic-atom-color-4: #39d353 !default;\n\n/* Element UI\n-------------------------- */\n$--dialog-background: #fff !default;\n$--table-border-color: #ebeef5 !default;\n"
  },
  {
    "path": "src/renderer/components/Theme/Variables.scss",
    "content": "/* Element Chalk Variables */\n\n// Special comment for theme configurator\n// type|skipAutoTranslation|Category|Order\n// skipAutoTranslation 1\n\n/* Transition\n-------------------------- */\n$--all-transition: all .25s cubic-bezier(.645,.045,.355,1) !default;\n$--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;\n$--fade-linear-transition: opacity 200ms linear !default;\n$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;\n$--border-transition-base: border-color .25s cubic-bezier(.645,.045,.355,1) !default;\n$--color-transition-base: color .25s cubic-bezier(.645,.045,.355,1) !default;\n\n/* Color\n-------------------------- */\n/// color|1|Brand Color|0\n$--color-primary: #5b5bfa !default;\n/// color|1|Background Color|4\n$--color-white: #FFFFFF !default;\n/// color|1|Background Color|4\n$--color-black: #000000 !default;\n$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */\n$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */\n$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */\n$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */\n$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */\n$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */\n$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */\n$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */\n$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */\n/// color|1|Functional Color|1\n$--color-success: #67C23A !default;\n/// color|1|Functional Color|1\n$--color-warning: #E6A23C !default;\n/// color|1|Functional Color|1\n$--color-danger: #F56C6C !default;\n/// color|1|Functional Color|1\n$--color-info: #909399 !default;\n\n$--color-success-light: mix($--color-white, $--color-success, 80%) !default;\n$--color-warning-light: mix($--color-white, $--color-warning, 80%) !default;\n$--color-danger-light: mix($--color-white, $--color-danger, 80%) !default;\n$--color-info-light: mix($--color-white, $--color-info, 80%) !default;\n\n$--color-success-lighter: mix($--color-white, $--color-success, 90%) !default;\n$--color-warning-lighter: mix($--color-white, $--color-warning, 90%) !default;\n$--color-danger-lighter: mix($--color-white, $--color-danger, 90%) !default;\n$--color-info-lighter: mix($--color-white, $--color-info, 90%) !default;\n/// color|1|Font Color|2\n$--color-text-primary: #303133 !default;\n/// color|1|Font Color|2\n$--color-text-regular: #606266 !default;\n/// color|1|Font Color|2\n$--color-text-secondary: #909399 !default;\n/// color|1|Font Color|2\n$--color-text-placeholder: #C0C4CC !default;\n/// color|1|Border Color|3\n$--border-color-base: #DCDFE6 !default;\n/// color|1|Border Color|3\n$--border-color-light: #E4E7ED !default;\n/// color|1|Border Color|3\n$--border-color-lighter: #EBEEF5 !default;\n/// color|1|Border Color|3\n$--border-color-extra-light: #F2F6FC !default;\n\n// Background\n/// color|1|Background Color|4\n$--background-color-base: #F5F7FA !default;\n\n/* Link\n-------------------------- */\n$--link-color: $--color-primary-light-2 !default;\n$--link-hover-color: $--color-primary !default;\n\n/* Border\n-------------------------- */\n$--border-width-base: 1px !default;\n$--border-style-base: solid !default;\n$--border-color-hover: $--color-text-placeholder !default;\n$--border-base: $--border-width-base $--border-style-base $--border-color-base !default;\n/// borderRadius|1|Radius|0\n$--border-radius-base: 4px !default;\n/// borderRadius|1|Radius|0\n$--border-radius-small: 2px !default;\n/// borderRadius|1|Radius|0\n$--border-radius-circle: 100% !default;\n/// borderRadius|1|Radius|0\n$--border-radius-zero: 0 !default;\n\n// Box-shadow\n/// boxShadow|1|Shadow|1\n$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04) !default;\n// boxShadow|1|Shadow|1\n$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .12) !default;\n/// boxShadow|1|Shadow|1\n$--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !default;\n\n/* Fill\n-------------------------- */\n$--fill-base: $--color-white !default;\n\n/* Typography\n-------------------------- */\n$--font-path: 'fonts' !default;\n/// fontSize|1|Font Size|0\n$--font-size-extra-large: 20px !default;\n/// fontSize|1|Font Size|0\n$--font-size-large: 18px !default;\n/// fontSize|1|Font Size|0\n$--font-size-medium: 16px !default;\n/// fontSize|1|Font Size|0\n$--font-size-base: 14px !default;\n/// fontSize|1|Font Size|0\n$--font-size-small: 13px !default;\n/// fontSize|1|Font Size|0\n$--font-size-extra-small: 12px !default;\n/// fontWeight|1|Font Weight|1\n$--font-weight-primary: 500 !default;\n/// fontWeight|1|Font Weight|1\n$--font-weight-secondary: 100 !default;\n/// fontLineHeight|1|Line Height|2\n$--font-line-height-primary: 24px !default;\n/// fontLineHeight|1|Line Height|2\n$--font-line-height-secondary: 16px !default;\n$--font-color-disabled-base: #bbb !default;\n/* Size\n-------------------------- */\n$--size-base: 14px !default;\n\n/* z-index\n-------------------------- */\n$--index-normal: 1 !default;\n$--index-top: 1000 !default;\n$--index-popper: 2100 !default;\n\n/* Disable base\n-------------------------- */\n$--disabled-fill-base: $--background-color-base !default;\n$--disabled-color-base: $--color-text-placeholder !default;\n$--disabled-border-base: $--border-color-light !default;\n\n/* Icon\n-------------------------- */\n$--icon-color: #666 !default;\n$--icon-color-base: $--color-info !default;\n\n/* Checkbox\n-------------------------- */\n/// fontSize||Font|1\n$--checkbox-font-size: 14px !default;\n/// fontWeight||Font|1\n$--checkbox-font-weight: normal !default;\n/// color||Color|0\n$--checkbox-font-color: $--color-text-regular !default;\n$--checkbox-input-height: 14px !default;\n$--checkbox-input-width: 14px !default;\n/// borderRadius||Border|2\n$--checkbox-border-radius: $--border-radius-small !default;\n/// color||Color|0\n$--checkbox-background-color: $--color-white !default;\n$--checkbox-input-border: $--border-base !default;\n\n/// color||Color|0\n$--checkbox-disabled-border-color: $--border-color-base !default;\n$--checkbox-disabled-input-fill: #edf2fc !default;\n$--checkbox-disabled-icon-color: $--color-text-placeholder !default;\n\n$--checkbox-disabled-checked-input-fill: $--border-color-extra-light !default;\n$--checkbox-disabled-checked-input-border-color: $--border-color-base !default;\n$--checkbox-disabled-checked-icon-color: $--color-text-placeholder !default;\n\n/// color||Color|0\n$--checkbox-checked-font-color: $--color-primary !default;\n$--checkbox-checked-input-border-color: $--color-primary !default;\n/// color||Color|0\n$--checkbox-checked-background-color: $--color-primary !default;\n$--checkbox-checked-icon-color: $--fill-base !default;\n\n$--checkbox-input-border-color-hover: $--color-primary !default;\n/// height||Other|4\n$--checkbox-bordered-height: 40px !default;\n/// padding||Spacing|3\n$--checkbox-bordered-padding: 9px 20px 9px 10px !default;\n/// padding||Spacing|3\n$--checkbox-bordered-medium-padding: 7px 20px 7px 10px !default;\n/// padding||Spacing|3\n$--checkbox-bordered-small-padding: 5px 15px 5px 10px !default;\n/// padding||Spacing|3\n$--checkbox-bordered-mini-padding: 3px 15px 3px 10px !default;\n$--checkbox-bordered-medium-input-height: 14px !default;\n$--checkbox-bordered-medium-input-width: 14px !default;\n/// height||Other|4\n$--checkbox-bordered-medium-height: 36px !default;\n$--checkbox-bordered-small-input-height: 12px !default;\n$--checkbox-bordered-small-input-width: 12px !default;\n/// height||Other|4\n$--checkbox-bordered-small-height: 32px !default;\n$--checkbox-bordered-mini-input-height: 12px !default;\n$--checkbox-bordered-mini-input-width: 12px !default;\n/// height||Other|4\n$--checkbox-bordered-mini-height: 28px !default;\n\n/// color||Color|0\n$--checkbox-button-checked-background-color: $--color-primary !default;\n/// color||Color|0\n$--checkbox-button-checked-font-color: $--color-white !default;\n/// color||Color|0\n$--checkbox-button-checked-border-color: $--color-primary !default;\n\n\n\n/* Radio\n-------------------------- */\n/// fontSize||Font|1\n$--radio-font-size: $--font-size-base !default;\n/// fontWeight||Font|1\n$--radio-font-weight: $--font-weight-primary !default;\n/// color||Color|0\n$--radio-font-color: $--color-text-regular !default;\n$--radio-input-height: 14px !default;\n$--radio-input-width: 14px !default;\n/// borderRadius||Border|2\n$--radio-input-border-radius: $--border-radius-circle !default;\n/// color||Color|0\n$--radio-input-background-color: $--color-white !default;\n$--radio-input-border: $--border-base !default;\n/// color||Color|0\n$--radio-input-border-color: $--border-color-base !default;\n/// color||Color|0\n$--radio-icon-color: $--color-white !default;\n\n$--radio-disabled-input-border-color: $--disabled-border-base !default;\n$--radio-disabled-input-fill: $--disabled-fill-base !default;\n$--radio-disabled-icon-color: $--disabled-fill-base !default;\n\n$--radio-disabled-checked-input-border-color: $--disabled-border-base !default;\n$--radio-disabled-checked-input-fill: $--disabled-fill-base !default;\n$--radio-disabled-checked-icon-color: $--color-text-placeholder !default;\n\n/// color||Color|0\n$--radio-checked-font-color: $--color-primary !default;\n/// color||Color|0\n$--radio-checked-input-border-color: $--color-primary !default;\n/// color||Color|0\n$--radio-checked-input-background-color: $--color-white !default;\n/// color||Color|0\n$--radio-checked-icon-color: $--color-primary !default;\n\n$--radio-input-border-color-hover: $--color-primary !default;\n\n$--radio-bordered-height: 40px !default;\n$--radio-bordered-padding: 12px 20px 0 10px !default;\n$--radio-bordered-medium-padding: 10px 20px 0 10px !default;\n$--radio-bordered-small-padding: 8px 15px 0 10px !default;\n$--radio-bordered-mini-padding: 6px 15px 0 10px !default;\n$--radio-bordered-medium-input-height: 14px !default;\n$--radio-bordered-medium-input-width: 14px !default;\n$--radio-bordered-medium-height: 36px !default;\n$--radio-bordered-small-input-height: 12px !default;\n$--radio-bordered-small-input-width: 12px !default;\n$--radio-bordered-small-height: 32px !default;\n$--radio-bordered-mini-input-height: 12px !default;\n$--radio-bordered-mini-input-width: 12px !default;\n$--radio-bordered-mini-height: 28px !default;\n\n/// fontSize||Font|1\n$--radio-button-font-size: $--font-size-base !default;\n/// color||Color|0\n$--radio-button-checked-background-color: $--color-primary !default;\n/// color||Color|0\n$--radio-button-checked-font-color: $--color-white !default;\n/// color||Color|0\n$--radio-button-checked-border-color: $--color-primary !default;\n$--radio-button-disabled-checked-fill: $--border-color-extra-light !default;\n\n/* Select\n-------------------------- */\n$--select-border-color-hover: $--border-color-hover !default;\n$--select-disabled-border: $--disabled-border-base !default;\n/// fontSize||Font|1\n$--select-font-size: $--font-size-base !default;\n$--select-close-hover-color: $--color-text-secondary !default;\n\n$--select-input-color: $--color-text-placeholder !default;\n$--select-multiple-input-color: #666 !default;\n/// color||Color|0\n$--select-input-focus-border-color: $--color-primary !default;\n/// fontSize||Font|1\n$--select-input-font-size: 14px !default;\n\n$--select-option-color: $--color-text-regular !default;\n$--select-option-disabled-color: $--color-text-placeholder !default;\n$--select-option-disabled-background: $--color-white !default;\n/// height||Other|4\n$--select-option-height: 34px !default;\n$--select-option-hover-background: $--background-color-base !default;\n/// color||Color|0\n$--select-option-selected-font-color: $--color-primary !default;\n$--select-option-selected-hover: $--background-color-base !default;\n\n$--select-group-color: $--color-info !default;\n$--select-group-height: 30px !default;\n$--select-group-font-size: 12px !default;\n\n$--select-dropdown-background: $--color-white !default;\n$--select-dropdown-shadow: $--box-shadow-light !default;\n$--select-dropdown-empty-color: #999 !default;\n/// height||Other|4\n$--select-dropdown-max-height: 274px !default;\n$--select-dropdown-padding: 6px 0 !default;\n$--select-dropdown-empty-padding: 10px 0 !default;\n$--select-dropdown-border: solid 1px $--border-color-light !default;\n\n/* Alert\n-------------------------- */\n$--alert-padding: 8px 16px !default;\n/// borderRadius||Border|2\n$--alert-border-radius: $--border-radius-base !default;\n/// fontSize||Font|1\n$--alert-title-font-size: 13px !default;\n/// fontSize||Font|1\n$--alert-description-font-size: 12px !default;\n/// fontSize||Font|1\n$--alert-close-font-size: 12px !default;\n/// fontSize||Font|1\n$--alert-close-customed-font-size: 13px !default;\n\n$--alert-success-color: $--color-success-lighter !default;\n$--alert-info-color: $--color-info-lighter !default;\n$--alert-warning-color: $--color-warning-lighter !default;\n$--alert-danger-color: $--color-danger-lighter !default;\n\n/// height||Other|4\n$--alert-icon-size: 16px !default;\n/// height||Other|4\n$--alert-icon-large-size: 28px !default;\n\n/* MessageBox\n-------------------------- */\n/// color||Color|0\n$--messagebox-title-color: $--color-text-primary !default;\n$--msgbox-width: 420px !default;\n$--msgbox-border-radius: 4px !default;\n/// fontSize||Font|1\n$--messagebox-font-size: $--font-size-large !default;\n/// fontSize||Font|1\n$--messagebox-content-font-size: $--font-size-base !default;\n/// color||Color|0\n$--messagebox-content-color: $--color-text-regular !default;\n/// fontSize||Font|1\n$--messagebox-error-font-size: 12px !default;\n$--msgbox-padding-primary: 15px !default;\n/// color||Color|0\n$--messagebox-success-color: $--color-success !default;\n/// color||Color|0\n$--messagebox-info-color: $--color-info !default;\n/// color||Color|0\n$--messagebox-warning-color: $--color-warning !default;\n/// color||Color|0\n$--messagebox-danger-color: $--color-danger !default;\n\n/* Message\n-------------------------- */\n$--message-shadow: $--box-shadow-base !default;\n$--message-min-width: 380px !default;\n$--message-background-color: #edf2fc !default;\n$--message-padding: 15px 15px 15px 20px !default;\n/// color||Color|0\n$--message-close-icon-color: $--color-text-placeholder !default;\n/// height||Other|4\n$--message-close-size: 16px !default;\n/// color||Color|0\n$--message-close-hover-color: $--color-text-secondary !default;\n\n/// color||Color|0\n$--message-success-font-color: $--color-success !default;\n/// color||Color|0\n$--message-info-font-color: $--color-info !default;\n/// color||Color|0\n$--message-warning-font-color: $--color-warning !default;\n/// color||Color|0\n$--message-danger-font-color: $--color-danger !default;\n\n/* Notification\n-------------------------- */\n$--notification-width: 330px !default;\n/// padding||Spacing|3\n$--notification-padding: 14px 26px 14px 13px !default;\n$--notification-radius: 8px !default;\n$--notification-shadow: $--box-shadow-light !default;\n/// color||Color|0\n$--notification-border-color: $--border-color-lighter !default;\n$--notification-icon-size: 24px !default;\n$--notification-close-font-size: $--message-close-size !default;\n$--notification-group-margin-left: 13px !default;\n$--notification-group-margin-right: 8px !default;\n/// fontSize||Font|1\n$--notification-content-font-size: $--font-size-base !default;\n/// color||Color|0\n$--notification-content-color: $--color-text-regular !default;\n/// fontSize||Font|1\n$--notification-title-font-size: 16px !default;\n/// color||Color|0\n$--notification-title-color: $--color-text-primary !default;\n\n/// color||Color|0\n$--notification-close-color: $--color-text-secondary !default;\n/// color||Color|0\n$--notification-close-hover-color: $--color-text-regular !default;\n\n/// color||Color|0\n$--notification-success-icon-color: $--color-success !default;\n/// color||Color|0\n$--notification-info-icon-color: $--color-info !default;\n/// color||Color|0\n$--notification-warning-icon-color: $--color-warning !default;\n/// color||Color|0\n$--notification-danger-icon-color: $--color-danger !default;\n\n/* Input\n-------------------------- */\n$--input-font-size: $--font-size-base !default;\n/// color||Color|0\n$--input-font-color: $--color-text-regular !default;\n/// height||Other|4\n$--input-width: 140px !default;\n/// height||Other|4\n$--input-height: 40px !default;\n$--input-border: $--border-base !default;\n$--input-border-color: $--border-color-base !default;\n/// borderRadius||Border|2\n$--input-border-radius: $--border-radius-base !default;\n$--input-border-color-hover: $--border-color-hover !default;\n/// color||Color|0\n$--input-background-color: $--color-white !default;\n$--input-fill-disabled: $--disabled-fill-base !default;\n$--input-color-disabled: $--font-color-disabled-base !default;\n/// color||Color|0\n$--input-icon-color: $--color-text-placeholder !default;\n/// color||Color|0\n$--input-placeholder-color: $--color-text-placeholder !default;\n$--input-max-width: 314px !default;\n\n$--input-hover-border: $--border-color-hover !default;\n$--input-clear-hover-color: $--color-text-secondary !default;\n\n$--input-focus-border: $--color-primary !default;\n$--input-focus-fill: $--color-white !default;\n\n$--input-disabled-fill: $--disabled-fill-base !default;\n$--input-disabled-border: $--disabled-border-base !default;\n$--input-disabled-color: $--disabled-color-base !default;\n$--input-disabled-placeholder-color: $--color-text-placeholder !default;\n\n/// fontSize||Font|1\n$--input-medium-font-size: 14px !default;\n/// height||Other|4\n$--input-medium-height: 36px !default;\n/// fontSize||Font|1\n$--input-small-font-size: 13px !default;\n/// height||Other|4\n$--input-small-height: 32px !default;\n/// fontSize||Font|1\n$--input-mini-font-size: 12px !default;\n/// height||Other|4\n$--input-mini-height: 28px !default;\n\n/* Cascader\n-------------------------- */\n/// color||Color|0\n$--cascader-menu-font-color: $--color-text-regular !default;\n/// color||Color|0\n$--cascader-menu-selected-font-color: $--color-primary !default;\n$--cascader-menu-fill: $--fill-base !default;\n$--cascader-menu-font-size: $--font-size-base !default;\n$--cascader-menu-radius: $--border-radius-base !default;\n$--cascader-menu-border: solid 1px $--border-color-light !default;\n$--cascader-menu-shadow: $--box-shadow-light !default;\n$--cascader-node-background-hover: $--background-color-base !default;\n$--cascader-node-color-disabled:$--color-text-placeholder !default;\n$--cascader-color-empty:$--color-text-placeholder !default;\n$--cascader-tag-background: #f0f2f5;\n\n/* Group\n-------------------------- */\n$--group-option-flex: 0 0 (0.2) * 100% !default;\n$--group-option-offset-bottom: 12px !default;\n$--group-option-fill-hover: rgba($--color-black, 0.06) !default;\n$--group-title-color: $--color-black !default;\n$--group-title-font-size: $--font-size-base !default;\n$--group-title-width: 66px !default;\n\n/* Tab\n-------------------------- */\n$--tab-font-size: $--font-size-base !default;\n$--tab-border-line: 1px solid #e4e4e4 !default;\n$--tab-header-color-active: $--color-text-secondary !default;\n$--tab-header-color-hover: $--color-text-regular !default;\n$--tab-header-color: $--color-text-regular !default;\n$--tab-header-fill-active: rgba($--color-black, 0.06) !default;\n$--tab-header-fill-hover: rgba($--color-black, 0.06) !default;\n$--tab-vertical-header-width: 90px !default;\n$--tab-vertical-header-count-color: $--color-white !default;\n$--tab-vertical-header-count-fill: $--color-text-secondary !default;\n\n/* Button\n-------------------------- */\n/// fontSize||Font|1\n$--button-font-size: $--font-size-base !default;\n/// fontWeight||Font|1\n$--button-font-weight: $--font-weight-primary !default;\n/// borderRadius||Border|2\n$--button-border-radius: $--border-radius-base !default;\n/// padding||Spacing|3\n$--button-padding-vertical: 12px !default;\n/// padding||Spacing|3\n$--button-padding-horizontal: 20px !default;\n\n/// fontSize||Font|1\n$--button-medium-font-size: $--font-size-base !default;\n/// borderRadius||Border|2\n$--button-medium-border-radius: $--border-radius-base !default;\n/// padding||Spacing|3\n$--button-medium-padding-vertical: 10px !default;\n/// padding||Spacing|3\n$--button-medium-padding-horizontal: 20px !default;\n\n/// fontSize||Font|1\n$--button-small-font-size: 12px !default;\n$--button-small-border-radius: #{$--border-radius-base - 1} !default;\n/// padding||Spacing|3\n$--button-small-padding-vertical: 9px !default;\n/// padding||Spacing|3\n$--button-small-padding-horizontal: 15px !default;\n/// fontSize||Font|1\n$--button-mini-font-size: 12px !default;\n$--button-mini-border-radius: #{$--border-radius-base - 1} !default;\n/// padding||Spacing|3\n$--button-mini-padding-vertical: 7px !default;\n/// padding||Spacing|3\n$--button-mini-padding-horizontal: 15px !default;\n\n/// color||Color|0\n$--button-default-font-color: $--color-text-regular !default;\n/// color||Color|0\n$--button-default-background-color: $--color-white !default;\n/// color||Color|0\n$--button-default-border-color: $--border-color-base !default;\n\n/// color||Color|0\n$--button-disabled-font-color: $--color-text-placeholder !default;\n/// color||Color|0\n$--button-disabled-background-color: $--color-white !default;\n/// color||Color|0\n$--button-disabled-border-color: $--border-color-lighter !default;\n\n/// color||Color|0\n$--button-primary-border-color: $--color-primary !default;\n/// color||Color|0\n$--button-primary-font-color: $--color-white !default;\n/// color||Color|0\n$--button-primary-background-color: $--color-primary !default;\n/// color||Color|0\n$--button-success-border-color: $--color-success !default;\n/// color||Color|0\n$--button-success-font-color: $--color-white !default;\n/// color||Color|0\n$--button-success-background-color: $--color-success !default;\n/// color||Color|0\n$--button-warning-border-color: $--color-warning !default;\n/// color||Color|0\n$--button-warning-font-color: $--color-white !default;\n/// color||Color|0\n$--button-warning-background-color: $--color-warning !default;\n/// color||Color|0\n$--button-danger-border-color: $--color-danger !default;\n/// color||Color|0\n$--button-danger-font-color: $--color-white !default;\n/// color||Color|0\n$--button-danger-background-color: $--color-danger !default;\n/// color||Color|0\n$--button-info-border-color: $--color-info !default;\n/// color||Color|0\n$--button-info-font-color: $--color-white !default;\n/// color||Color|0\n$--button-info-background-color: $--color-info !default;\n\n$--button-hover-tint-percent: 20% !default;\n$--button-active-shade-percent: 10% !default;\n\n\n/* cascader\n-------------------------- */\n$--cascader-height: 200px !default;\n\n/* Switch\n-------------------------- */\n/// color||Color|0\n$--switch-on-color: $--color-primary !default;\n/// color||Color|0\n$--switch-off-color: $--border-color-base !default;\n/// fontSize||Font|1\n$--switch-font-size: $--font-size-base !default;\n$--switch-core-border-radius: 10px !default;\n// height||Other|4 TODO: width 代码写死的40px 所以下面这三个属性都没意义\n$--switch-width: 40px !default;\n// height||Other|4\n$--switch-height: 20px !default;\n// height||Other|4\n$--switch-button-size: 16px !default;\n\n/* Dialog\n-------------------------- */\n$--dialog-background-color: $--color-white !default;\n$--dialog-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !default;\n/// fontSize||Font|1\n$--dialog-title-font-size: $--font-size-large !default;\n/// fontSize||Font|1\n$--dialog-content-font-size: 14px !default;\n/// fontLineHeight||LineHeight|2\n$--dialog-font-line-height: $--font-line-height-primary !default;\n/// padding||Spacing|3\n$--dialog-padding-primary: 20px !default;\n\n/* Table\n-------------------------- */\n/// color||Color|0\n$--table-border-color: $--border-color-lighter !default;\n$--table-border: 1px solid $--table-border-color !default;\n/// color||Color|0\n$--table-font-color: $--color-text-regular !default;\n/// color||Color|0\n$--table-header-font-color: $--color-text-secondary !default;\n/// color||Color|0\n$--table-row-hover-background-color: $--background-color-base !default;\n$--table-current-row-background-color: $--color-primary-light-9 !default;\n/// color||Color|0\n$--table-header-background-color: $--color-white !default;\n$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, .12) !default;\n\n/* Pagination\n-------------------------- */\n/// fontSize||Font|1\n$--pagination-font-size: 13px !default;\n/// color||Color|0\n$--pagination-background-color: $--color-white !default;\n/// color||Color|0\n$--pagination-font-color: $--color-text-primary !default;\n$--pagination-border-radius: 3px !default;\n/// color||Color|0\n$--pagination-button-color: $--color-text-primary !default;\n/// height||Other|4\n$--pagination-button-width: 35.5px !default;\n/// height||Other|4\n$--pagination-button-height: 28px !default;\n/// color||Color|0\n$--pagination-button-disabled-color: $--color-text-placeholder !default;\n/// color||Color|0\n$--pagination-button-disabled-background-color: $--color-white !default;\n/// color||Color|0\n$--pagination-hover-color: $--color-primary !default;\n\n/* Popup\n-------------------------- */\n/// color||Color|0\n$--popup-modal-background-color: $--color-black !default;\n/// opacity||Other|1\n$--popup-modal-opacity: 0.5 !default;\n\n/* Popover\n-------------------------- */\n/// color||Color|0\n$--popover-background-color: $--color-white !default;\n/// fontSize||Font|1\n$--popover-font-size: $--font-size-base !default;\n/// color||Color|0\n$--popover-border-color: $--border-color-lighter !default;\n$--popover-arrow-size: 6px !default;\n/// padding||Spacing|3\n$--popover-padding: 12px !default;\n$--popover-padding-large: 18px 20px !default;\n/// fontSize||Font|1\n$--popover-title-font-size: 16px !default;\n/// color||Color|0\n$--popover-title-font-color: $--color-text-primary !default;\n\n/* Tooltip\n-------------------------- */\n/// color|1|Color|0\n$--tooltip-fill: $--color-text-primary !default;\n/// color|1|Color|0\n$--tooltip-color: $--color-white !default;\n/// fontSize||Font|1\n$--tooltip-font-size: 12px !default;\n/// color||Color|0\n$--tooltip-border-color: $--color-text-primary !default;\n$--tooltip-arrow-size: 6px !default;\n/// padding||Spacing|3\n$--tooltip-padding: 10px !default;\n\n/* Tag\n-------------------------- */\n/// color||Color|0\n$--tag-info-color: $--color-info !default;\n/// color||Color|0\n$--tag-primary-color: $--color-primary !default;\n/// color||Color|0\n$--tag-success-color: $--color-success !default;\n/// color||Color|0\n$--tag-warning-color: $--color-warning !default;\n/// color||Color|0\n$--tag-danger-color: $--color-danger !default;\n/// fontSize||Font|1\n$--tag-font-size: 12px !default;\n$--tag-border-radius: 4px !default;\n$--tag-padding: 0 10px !default;\n\n/* Tree\n-------------------------- */\n/// color||Color|0\n$--tree-node-hover-background-color: $--background-color-base !default;\n/// color||Color|0\n$--tree-font-color: $--color-text-regular !default;\n/// color||Color|0\n$--tree-expand-icon-color: $--color-text-placeholder !default;\n\n/* Dropdown\n-------------------------- */\n$--dropdown-menu-box-shadow: $--box-shadow-light !default;\n$--dropdown-menuItem-hover-fill: $--color-primary-light-9 !default;\n$--dropdown-menuItem-hover-color: $--link-color !default;\n\n/* Badge\n-------------------------- */\n/// color||Color|0\n$--badge-background-color: $--color-danger !default;\n$--badge-radius: 10px !default;\n/// fontSize||Font|1\n$--badge-font-size: 12px !default;\n/// padding||Spacing|3\n$--badge-padding: 6px !default;\n/// height||Other|4\n$--badge-size: 18px !default;\n\n/* Card\n--------------------------*/\n/// color||Color|0\n$--card-border-color: $--border-color-lighter !default;\n$--card-border-radius: 4px !default;\n/// padding||Spacing|3\n$--card-padding: 20px !default;\n\n/* Slider\n--------------------------*/\n/// color||Color|0\n$--slider-main-background-color: $--color-primary !default;\n/// color||Color|0\n$--slider-runway-background-color: $--border-color-light !default;\n$--slider-button-hover-color: mix($--color-primary, black, 97%) !default;\n$--slider-stop-background-color: $--color-white !default;\n$--slider-disable-color: $--color-text-placeholder !default;\n$--slider-margin: 16px 0 !default;\n$--slider-border-radius: 3px !default;\n/// height|1|Other|4\n$--slider-height: 6px !default;\n/// height||Other|4\n$--slider-button-size: 16px !default;\n$--slider-button-wrapper-size: 36px !default;\n$--slider-button-wrapper-offset: -15px !default;\n\n/* Steps\n--------------------------*/\n$--steps-border-color: $--disabled-border-base !default;\n$--steps-border-radius: 4px !default;\n$--steps-padding: 20px !default;\n\n/* Menu\n--------------------------*/\n/// fontSize||Font|1\n$--menu-item-font-size: $--font-size-base !default;\n/// color||Color|0\n$--menu-item-font-color: $--color-text-primary !default;\n/// color||Color|0\n$--menu-background-color: $--color-white !default;\n$--menu-item-hover-fill: $--color-primary-light-9 !default;\n\n/* Rate\n--------------------------*/\n$--rate-height: 20px !default;\n/// fontSize||Font|1\n$--rate-font-size: $--font-size-base !default;\n/// height||Other|3\n$--rate-icon-size: 18px !default;\n/// margin||Spacing|2\n$--rate-icon-margin: 6px !default;\n$--rate-icon-color: $--color-text-placeholder !default;\n\n/* DatePicker\n--------------------------*/\n$--datepicker-font-color: $--color-text-regular !default;\n/// color|1|Color|0\n$--datepicker-off-font-color: $--color-text-placeholder !default;\n/// color||Color|0\n$--datepicker-header-font-color: $--color-text-regular !default;\n$--datepicker-icon-color: $--color-text-primary !default;\n$--datepicker-border-color: $--disabled-border-base !default;\n$--datepicker-inner-border-color: #e4e4e4 !default;\n/// color||Color|0\n$--datepicker-inrange-background-color: $--border-color-extra-light !default;\n/// color||Color|0\n$--datepicker-inrange-hover-background-color: $--border-color-extra-light !default;\n/// color||Color|0\n$--datepicker-active-color: $--color-primary !default;\n/// color||Color|0\n$--datepicker-hover-font-color: $--color-primary !default;\n$--datepicker-cell-hover-color: #fff !default;\n\n/* Loading\n--------------------------*/\n/// height||Other|4\n$--loading-spinner-size: 42px !default;\n/// height||Other|4\n$--loading-fullscreen-spinner-size: 50px !default;\n\n/* Scrollbar\n--------------------------*/\n$--scrollbar-background-color: rgba($--color-text-secondary, .3) !default;\n$--scrollbar-hover-background-color: rgba($--color-text-secondary, .5) !default;\n\n/* Carousel\n--------------------------*/\n/// fontSize||Font|1\n$--carousel-arrow-font-size: 12px !default;\n$--carousel-arrow-size: 36px !default;\n$--carousel-arrow-background: rgba(31, 45, 61, 0.11) !default;\n$--carousel-arrow-hover-background: rgba(31, 45, 61, 0.23) !default;\n/// width||Other|4\n$--carousel-indicator-width: 30px !default;\n/// height||Other|4\n$--carousel-indicator-height: 2px !default;\n$--carousel-indicator-padding-horizontal: 4px !default;\n$--carousel-indicator-padding-vertical: 12px !default;\n$--carousel-indicator-out-color: $--border-color-hover !default;\n\n/* Collapse\n--------------------------*/\n/// color||Color|0\n$--collapse-border-color: $--border-color-lighter !default;\n/// height||Other|4\n$--collapse-header-height: 48px !default;\n/// color||Color|0\n$--collapse-header-background-color: $--color-white !default;\n/// color||Color|0\n$--collapse-header-font-color: $--color-text-primary !default;\n/// fontSize||Font|1\n$--collapse-header-font-size: 13px !default;\n/// color||Color|0\n$--collapse-content-background-color: $--color-white !default;\n/// fontSize||Font|1\n$--collapse-content-font-size: 13px !default;\n/// color||Color|0\n$--collapse-content-font-color: $--color-text-primary !default;\n\n/* Transfer\n--------------------------*/\n$--transfer-border-color: $--border-color-lighter !default;\n$--transfer-border-radius: $--border-radius-base !default;\n/// height||Other|4\n$--transfer-panel-width: 200px !default;\n/// height||Other|4\n$--transfer-panel-header-height: 40px !default;\n/// color||Color|0\n$--transfer-panel-header-background-color: $--background-color-base !default;\n/// height||Other|4\n$--transfer-panel-footer-height: 40px !default;\n/// height||Other|4\n$--transfer-panel-body-height: 246px !default;\n/// height||Other|4\n$--transfer-item-height: 30px !default;\n/// height||Other|4\n$--transfer-filter-height: 32px !default;\n\n/* Header\n  --------------------------*/\n$--header-padding: 0 20px !default;\n\n/* Footer\n--------------------------*/\n$--footer-padding: 0 20px !default;\n\n/* Main\n--------------------------*/\n$--main-padding: 20px !default;\n\n/* Timeline\n--------------------------*/\n$--timeline-node-size-normal: 12px !default;\n$--timeline-node-size-large: 14px !default;\n$--timeline-node-color: $--border-color-light !default;\n\n/* Backtop\n--------------------------*/\n/// color||Color|0\n$--backtop-background-color: $--color-white !default;\n/// color||Color|0\n$--backtop-font-color: $--color-primary !default;\n/// color||Color|0\n$--backtop-hover-background-color: $--border-color-extra-light !default;\n\n/* Link\n--------------------------*/\n/// fontSize||Font|1\n$--link-font-size: $--font-size-base !default;\n/// fontWeight||Font|1\n$--link-font-weight: $--font-weight-primary !default;\n/// color||Color|0\n$--link-default-font-color: $--color-text-regular !default;\n/// color||Color|0\n$--link-default-active-color: $--color-primary !default;\n/// color||Color|0\n$--link-disabled-font-color: $--color-text-placeholder !default;\n/// color||Color|0\n$--link-primary-font-color: $--color-primary !default;\n/// color||Color|0\n$--link-success-font-color: $--color-success !default;\n/// color||Color|0\n$--link-warning-font-color: $--color-warning !default;\n/// color||Color|0\n$--link-danger-font-color: $--color-danger !default;\n/// color||Color|0\n$--link-info-font-color: $--color-info !default;\n/* Calendar\n--------------------------*/\n/// border||Other|4\n$--calendar-border: $--table-border !default;\n/// color||Other|4\n$--calendar-selected-background-color: #F2F8FE !default;\n$--calendar-cell-width: 85px !default;\n\n/* Break-point\n--------------------------*/\n$--sm: 768px !default;\n$--md: 992px !default;\n$--lg: 1200px !default;\n$--xl: 1920px !default;\n\n$--breakpoints: (\n  'xs' : (max-width: $--sm - 1),\n  'sm' : (min-width: $--sm),\n  'md' : (min-width: $--md),\n  'lg' : (min-width: $--lg),\n  'xl' : (min-width: $--xl)\n);\n\n$--breakpoints-spec: (\n  'xs-only' : (max-width: $--sm - 1),\n  'sm-and-up' : (min-width: $--sm),\n  'sm-only': \"(min-width: #{$--sm}) and (max-width: #{$--md - 1})\",\n  'sm-and-down': (max-width: $--md - 1),\n  'md-and-up' : (min-width: $--md),\n  'md-only': \"(min-width: #{$--md}) and (max-width: #{$--lg - 1})\",\n  'md-and-down': (max-width: $--lg - 1),\n  'lg-and-up' : (min-width: $--lg),\n  'lg-only': \"(min-width: #{$--lg}) and (max-width: #{$--xl - 1})\",\n  'lg-and-down': (max-width: $--xl - 1),\n  'xl-only' : (min-width: $--xl),\n);\n\n/* 改变 icon 字体路径变量，必需 */\n$--font-path: '~element-ui/lib/theme-chalk/fonts';\n\n/* Mixin\n-------------------------- */\n@mixin clearfix {\n  &:after {\n    content: \"\";\n    display: table;\n    clear: both;\n  }\n}\n\n/* App Theme Variables\n-------------------------- */\n@import './Light/Variables.scss';\n@import './Dark/Variables.scss';\n"
  },
  {
    "path": "src/renderer/pages/index/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <mo-title-bar\n      v-if=\"isRenderer\"\n      :showActions=\"showWindowActions\"\n    />\n    <router-view />\n    <mo-engine-client\n      :secret=\"rpcSecret\"\n    />\n    <mo-ipc v-if=\"isRenderer\" />\n    <mo-dynamic-tray v-if=\"enableTraySpeedometer\" />\n  </div>\n</template>\n\n<script>\n  import is from 'electron-is'\n  import { mapGetters, mapState } from 'vuex'\n  import { APP_RUN_MODE, APP_THEME } from '@shared/constants'\n  import DynamicTray from '@/components/Native/DynamicTray'\n  import EngineClient from '@/components/Native/EngineClient'\n  import Ipc from '@/components/Native/Ipc'\n  import TitleBar from '@/components/Native/TitleBar'\n  import { getLanguage } from '@shared/locales'\n  import { getLocaleManager } from '@/components/Locale'\n\n  export default {\n    name: 'motrix-app',\n    components: {\n      [DynamicTray.name]: DynamicTray,\n      [EngineClient.name]: EngineClient,\n      [Ipc.name]: Ipc,\n      [TitleBar.name]: TitleBar\n    },\n    computed: {\n      isMac: () => is.macOS(),\n      isRenderer: () => is.renderer(),\n      ...mapState('app', {\n        systemTheme: state => state.systemTheme\n      }),\n      ...mapState('preference', {\n        showWindowActions: state => {\n          return (is.windows() || is.linux()) && state.config.hideAppMenu\n        },\n        runMode: state => state.config.runMode,\n        traySpeedometer: state => state.config.traySpeedometer,\n        rpcSecret: state => state.config.rpcSecret\n      }),\n      ...mapGetters('preference', [\n        'theme',\n        'locale',\n        'direction'\n      ]),\n      themeClass () {\n        if (this.theme === APP_THEME.AUTO) {\n          return `theme-${this.systemTheme}`\n        } else {\n          return `theme-${this.theme}`\n        }\n      },\n      i18nClass () {\n        return `i18n-${this.locale}`\n      },\n      directionClass () {\n        return `dir-${this.direction}`\n      },\n      enableTraySpeedometer () {\n        const { isMac, isRenderer, traySpeedometer, runMode } = this\n        return isMac && isRenderer && traySpeedometer && runMode !== APP_RUN_MODE.HIDE_TRAY\n      }\n    },\n    methods: {\n      updateRootClassName () {\n        const { themeClass = '', i18nClass = '', directionClass = '' } = this\n        const className = `${themeClass} ${i18nClass} ${directionClass}`\n        document.documentElement.className = className\n      }\n    },\n    beforeMount () {\n      this.updateRootClassName()\n    },\n    watch: {\n      locale (val) {\n        const lng = getLanguage(val)\n        getLocaleManager().changeLanguage(lng)\n      },\n      themeClass () {\n        this.updateRootClassName()\n      },\n      i18nClass () {\n        this.updateRootClassName()\n      },\n      directionClass () {\n        this.updateRootClassName()\n      }\n    }\n  }\n</script>\n\n<style>\n</style>\n"
  },
  {
    "path": "src/renderer/pages/index/commands.js",
    "content": "import { Message } from 'element-ui'\nimport { base64StringToBlob } from 'blob-util'\n\nimport router from '@/router'\nimport store from '@/store'\nimport { buildFileList } from '@shared/utils'\nimport { ADD_TASK_TYPE } from '@shared/constants'\nimport { getLocaleManager } from '@/components/Locale'\nimport { commands } from '@/components/CommandManager/instance'\nimport {\n  initTaskForm,\n  buildUriPayload,\n  buildTorrentPayload\n} from '@/utils/task'\n\nconst i18n = getLocaleManager().getI18n()\n\nconst updateSystemTheme = (payload = {}) => {\n  const { theme } = payload\n  store.dispatch('app/updateSystemTheme', theme)\n}\n\nconst updateTheme = (payload = {}) => {\n  const { theme } = payload\n  store.dispatch('preference/updateAppTheme', theme)\n}\n\nconst updateLocale = (payload = {}) => {\n  const { locale } = payload\n  store.dispatch('preference/updateAppLocale', locale)\n}\n\nconst updateTrayFocused = (payload = {}) => {\n  const { focused } = payload\n  store.dispatch('app/updateTrayFocused', focused)\n}\n\nconst showAboutPanel = () => {\n  store.dispatch('app/showAboutPanel')\n}\n\nconst addTask = (payload = {}) => {\n  const {\n    type = ADD_TASK_TYPE.URI,\n    uri,\n    silent,\n    ...rest\n  } = payload\n\n  const options = {\n    ...rest\n  }\n\n  if (type === ADD_TASK_TYPE.URI && uri) {\n    store.dispatch('app/updateAddTaskUrl', uri)\n  }\n  store.dispatch('app/updateAddTaskOptions', options)\n\n  if (silent) {\n    addTaskSilent(type)\n    return\n  }\n\n  store.dispatch('app/showAddTaskDialog', type)\n}\n\nconst addTaskSilent = (type) => {\n  try {\n    addTaskByType(type)\n  } catch (err) {\n    Message.error(i18n.t(err.message))\n  }\n}\n\nconst addTaskByType = (type) => {\n  const form = initTaskForm(store.state)\n\n  let payload = null\n  if (type === ADD_TASK_TYPE.URI) {\n    payload = buildUriPayload(form)\n    store.dispatch('task/addUri', payload).catch(err => {\n      Message.error(err.message)\n    })\n  } else if (type === ADD_TASK_TYPE.TORRENT) {\n    payload = buildTorrentPayload(form)\n    store.dispatch('task/addTorrent', payload).catch(err => {\n      Message.error(err.message)\n    })\n  } else if (type === 'metalink') {\n  // @TODO addMetalink\n  } else {\n    console.error('addTask fail', form)\n  }\n}\n\nconst showAddBtTask = () => {\n  store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)\n}\n\nconst showAddBtTaskWithFile = (payload = {}) => {\n  const { name, dataURL = '' } = payload\n  if (!dataURL) {\n    return\n  }\n\n  const blob = base64StringToBlob(dataURL, 'application/x-bittorrent')\n  const file = new File([blob], name, { type: 'application/x-bittorrent' })\n  const fileList = buildFileList(file)\n\n  store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)\n  setTimeout(() => {\n    store.dispatch('app/addTaskAddTorrents', { fileList })\n  }, 200)\n}\n\nconst navigateTaskList = (payload = {}) => {\n  const { status = 'active' } = payload\n\n  router.push({ path: `/task/${status}` }).catch(err => {\n    console.log(err)\n  })\n}\n\nconst navigatePreferences = () => {\n  router.push({ path: '/preference' }).catch(err => {\n    console.log(err)\n  })\n}\n\nconst showUnderDevelopmentMessage = () => {\n  Message.info(i18n.t('app.under-development-message'))\n}\n\nconst pauseTask = () => {\n  store.dispatch('task/batchPauseSelectedTasks')\n}\n\nconst resumeTask = () => {\n  store.dispatch('task/batchResumeSelectedTasks')\n}\n\nconst deleteTask = () => {\n  commands.emit('batch-delete-task', {\n    deleteWithFiles: false\n  })\n}\n\nconst moveTaskUp = () => {\n  showUnderDevelopmentMessage()\n}\n\nconst moveTaskDown = () => {\n  showUnderDevelopmentMessage()\n}\n\nconst pauseAllTask = () => {\n  store.dispatch('task/pauseAllTask')\n}\n\nconst resumeAllTask = () => {\n  store.dispatch('task/resumeAllTask')\n}\n\nconst selectAllTask = () => {\n  store.dispatch('task/selectAllTask')\n}\n\nconst showTaskDetail = (payload = {}) => {\n  const { gid } = payload\n  navigateTaskList()\n  if (gid) {\n    store.dispatch('task/showTaskDetailByGid', gid)\n  }\n}\n\nconst fetchPreference = () => {\n  store.dispatch('preference/fetchPreference')\n}\n\ncommands.register('application:task-list', navigateTaskList)\ncommands.register('application:preferences', navigatePreferences)\ncommands.register('application:about', showAboutPanel)\n\ncommands.register('application:new-task', addTask)\ncommands.register('application:new-bt-task', showAddBtTask)\ncommands.register('application:new-bt-task-with-file', showAddBtTaskWithFile)\ncommands.register('application:pause-task', pauseTask)\ncommands.register('application:resume-task', resumeTask)\ncommands.register('application:delete-task', deleteTask)\ncommands.register('application:move-task-up', moveTaskUp)\ncommands.register('application:move-task-down', moveTaskDown)\ncommands.register('application:pause-all-task', pauseAllTask)\ncommands.register('application:resume-all-task', resumeAllTask)\ncommands.register('application:select-all-task', selectAllTask)\ncommands.register('application:show-task-detail', showTaskDetail)\n\ncommands.register('application:update-preference-config', fetchPreference)\ncommands.register('application:update-system-theme', updateSystemTheme)\ncommands.register('application:update-theme', updateTheme)\ncommands.register('application:update-locale', updateLocale)\ncommands.register('application:update-tray-focused', updateTrayFocused)\n"
  },
  {
    "path": "src/renderer/pages/index/main.js",
    "content": "import is from 'electron-is'\nimport { ipcRenderer } from 'electron'\nimport Vue from 'vue'\nimport VueI18Next from '@panter/vue-i18next'\nimport { sync } from 'vuex-router-sync'\nimport Element, { Loading, Message } from 'element-ui'\nimport axios from 'axios'\n\nimport App from './App'\nimport router from '@/router'\nimport store from '@/store'\nimport { getLocaleManager } from '@/components/Locale'\nimport Icon from '@/components/Icons/Icon'\nimport Msg from '@/components/Msg'\nimport { commands } from '@/components/CommandManager/instance'\nimport TrayWorker from '@/workers/tray.worker'\n\nimport '@/components/Theme/Index.scss'\n\nconst updateTray = is.renderer()\n  ? async (payload) => {\n    const { tray } = payload\n    if (!tray) {\n      return\n    }\n\n    const ab = await tray.arrayBuffer()\n    ipcRenderer.send('command', 'application:update-tray', ab)\n  }\n  : () => {}\n\nfunction initTrayWorker () {\n  const worker = new TrayWorker()\n\n  worker.addEventListener('message', (event) => {\n    const { type, payload } = event.data\n\n    switch (type) {\n    case 'initialized':\n    case 'log':\n      console.log('[Motrix] Log from Tray Worker: ', payload)\n      break\n    case 'tray:drawed':\n      updateTray(payload)\n      break\n    default:\n      console.warn('[Motrix] Tray Worker unhandled message type:', type, payload)\n    }\n  })\n\n  return worker\n}\n\nfunction init (config) {\n  if (is.renderer()) {\n    Vue.use(require('vue-electron'))\n  }\n\n  Vue.http = Vue.prototype.$http = axios\n  Vue.config.productionTip = false\n\n  const { locale } = config\n  const localeManager = getLocaleManager()\n  localeManager.changeLanguageByLocale(locale)\n\n  Vue.use(VueI18Next)\n  const i18n = new VueI18Next(localeManager.getI18n())\n  Vue.use(Element, {\n    size: 'mini',\n    i18n: (key, value) => i18n.t(key, value)\n  })\n  Vue.use(Msg, Message, {\n    showClose: true\n  })\n  Vue.component('mo-icon', Icon)\n\n  const loading = Loading.service({\n    fullscreen: true,\n    background: 'rgba(0, 0, 0, 0.1)'\n  })\n\n  sync(store, router)\n\n  /* eslint-disable no-new */\n  global.app = new Vue({\n    components: { App },\n    router,\n    store,\n    i18n,\n    template: '<App/>'\n  }).$mount('#app')\n\n  global.app.commands = commands\n  require('./commands')\n\n  global.app.trayWorker = initTrayWorker()\n\n  setTimeout(() => {\n    loading.close()\n  }, 400)\n}\n\nstore.dispatch('preference/fetchPreference')\n  .then((config) => {\n    console.info('[Motrix] load preference:', config)\n    init(config)\n  })\n  .catch((err) => {\n    alert(err)\n  })\n"
  },
  {
    "path": "src/renderer/router/index.js",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nVue.use(Router)\n\nexport default new Router({\n  routes: [\n    {\n      path: '/',\n      name: 'main',\n      component: require('@/components/Main').default,\n      children: [\n        {\n          path: '/task',\n          alias: '/',\n          component: require('@/components/Task/Index').default,\n          props: {\n            status: 'active'\n          }\n        },\n        {\n          path: '/task/:status',\n          name: 'task',\n          component: require('@/components/Task/Index').default,\n          props: true\n        },\n        {\n          path: '/preference',\n          name: 'preference',\n          component: require('@/components/Preference/Index').default,\n          props: true,\n          children: [\n            {\n              path: 'basic',\n              alias: '',\n              components: {\n                subnav: require('@/components/Subnav/PreferenceSubnav').default,\n                form: require('@/components/Preference/Basic').default\n              },\n              props: {\n                subnav: { current: 'basic' }\n              }\n            },\n            {\n              path: 'advanced',\n              components: {\n                subnav: require('@/components/Subnav/PreferenceSubnav').default,\n                form: require('@/components/Preference/Advanced').default\n              },\n              props: {\n                subnav: { current: 'advanced' }\n              }\n            },\n            {\n              path: 'lab',\n              components: {\n                subnav: require('@/components/Subnav/PreferenceSubnav').default,\n                form: require('@/components/Preference/Lab').default\n              },\n              props: {\n                subnav: { current: 'lab' }\n              }\n            }\n          ]\n        }\n      ]\n    },\n    {\n      path: '*',\n      redirect: '/'\n    }\n  ]\n})\n"
  },
  {
    "path": "src/renderer/store/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\n\nimport modules from './modules'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n  modules,\n  strict: process.env.NODE_ENV !== 'production'\n})\n"
  },
  {
    "path": "src/renderer/store/modules/app.js",
    "content": "import { ADD_TASK_TYPE } from '@shared/constants'\nimport api from '@/api'\nimport { getSystemTheme } from '@/utils/native'\n\nconst BASE_INTERVAL = 1000\nconst PER_INTERVAL = 100\nconst MIN_INTERVAL = 500\nconst MAX_INTERVAL = 6000\n\nconst state = {\n  systemTheme: getSystemTheme(),\n  trayFocused: false,\n  aboutPanelVisible: false,\n  engineInfo: {\n    version: '',\n    enabledFeatures: []\n  },\n  engineOptions: {},\n  interval: BASE_INTERVAL,\n  stat: {\n    downloadSpeed: 0,\n    uploadSpeed: 0,\n    numActive: 0,\n    numWaiting: 0,\n    numStopped: 0\n  },\n  addTaskVisible: false,\n  addTaskType: ADD_TASK_TYPE.URI,\n  addTaskUrl: '',\n  addTaskTorrents: [],\n  addTaskOptions: {},\n  progress: 0\n}\n\nconst getters = {\n}\n\nconst mutations = {\n  UPDATE_SYSTEM_THEME (state, theme) {\n    state.systemTheme = theme\n  },\n  UPDATE_TRAY_FOCUSED (state, focused) {\n    state.trayFocused = focused\n  },\n  UPDATE_ABOUT_PANEL_VISIBLE (state, visible) {\n    state.aboutPanelVisible = visible\n  },\n  UPDATE_ENGINE_INFO (state, engineInfo) {\n    state.engineInfo = { ...state.engineInfo, ...engineInfo }\n  },\n  UPDATE_ENGINE_OPTIONS (state, engineOptions) {\n    state.engineOptions = { ...state.engineOptions, ...engineOptions }\n  },\n  UPDATE_GLOBAL_STAT (state, stat) {\n    state.stat = stat\n  },\n  UPDATE_ADD_TASK_VISIBLE (state, visible) {\n    state.addTaskVisible = visible\n  },\n  UPDATE_ADD_TASK_TYPE (state, taskType) {\n    state.addTaskType = taskType\n  },\n  UPDATE_ADD_TASK_URL (state, text) {\n    state.addTaskUrl = text\n  },\n  UPDATE_ADD_TASK_TORRENTS (state, fileList) {\n    state.addTaskTorrents = [...fileList]\n  },\n  UPDATE_ADD_TASK_OPTIONS (state, options) {\n    state.addTaskOptions = {\n      ...options\n    }\n  },\n  UPDATE_INTERVAL (state, millisecond) {\n    let interval = millisecond\n    if (millisecond > MAX_INTERVAL) {\n      interval = MAX_INTERVAL\n    }\n    if (millisecond < MIN_INTERVAL) {\n      interval = MIN_INTERVAL\n    }\n    if (state.interval === interval) {\n      return\n    }\n    state.interval = interval\n  },\n  INCREASE_INTERVAL (state, millisecond) {\n    if (state.interval < MAX_INTERVAL) {\n      state.interval += millisecond\n    }\n  },\n  DECREASE_INTERVAL (state, millisecond) {\n    if (state.interval > MIN_INTERVAL) {\n      state.interval -= millisecond\n    }\n  },\n  UPDATE_PROGRESS (state, progress) {\n    state.progress = progress\n  }\n}\n\nconst actions = {\n  updateSystemTheme ({ commit }, theme) {\n    commit('UPDATE_SYSTEM_THEME', theme)\n  },\n  updateTrayFocused ({ commit }, focused) {\n    commit('UPDATE_TRAY_FOCUSED', focused)\n  },\n  showAboutPanel ({ commit }) {\n    commit('UPDATE_ABOUT_PANEL_VISIBLE', true)\n  },\n  hideAboutPanel ({ commit }) {\n    commit('UPDATE_ABOUT_PANEL_VISIBLE', false)\n  },\n  fetchEngineInfo ({ commit }) {\n    api.getVersion()\n      .then((data) => {\n        commit('UPDATE_ENGINE_INFO', data)\n      })\n  },\n  fetchEngineOptions ({ commit }) {\n    return new Promise((resolve) => {\n      api.getGlobalOption()\n        .then((data) => {\n          commit('UPDATE_ENGINE_OPTIONS', data)\n          resolve(data)\n        })\n    })\n  },\n  fetchGlobalStat ({ commit, dispatch }) {\n    api.getGlobalStat()\n      .then((data) => {\n        const stat = {}\n        Object.keys(data).forEach((key) => {\n          stat[key] = Number(data[key])\n        })\n\n        const { numActive } = stat\n        if (numActive > 0) {\n          const interval = BASE_INTERVAL - PER_INTERVAL * numActive\n          dispatch('updateInterval', interval)\n        } else {\n          // fix downloadSpeed when numActive = 0\n          stat.downloadSpeed = 0\n          dispatch('increaseInterval')\n        }\n        commit('UPDATE_GLOBAL_STAT', stat)\n      })\n  },\n  increaseInterval ({ commit }, millisecond = 100) {\n    commit('INCREASE_INTERVAL', millisecond)\n  },\n  showAddTaskDialog ({ commit }, taskType) {\n    commit('UPDATE_ADD_TASK_TYPE', taskType)\n    commit('UPDATE_ADD_TASK_VISIBLE', true)\n  },\n  hideAddTaskDialog ({ commit }) {\n    commit('UPDATE_ADD_TASK_VISIBLE', false)\n    commit('UPDATE_ADD_TASK_URL', '')\n    commit('UPDATE_ADD_TASK_TORRENTS', [])\n  },\n  changeAddTaskType ({ commit }, taskType) {\n    commit('UPDATE_ADD_TASK_TYPE', taskType)\n  },\n  updateAddTaskUrl ({ commit }, uri = '') {\n    commit('UPDATE_ADD_TASK_URL', uri)\n  },\n  addTaskAddTorrents ({ commit }, { fileList }) {\n    commit('UPDATE_ADD_TASK_TORRENTS', fileList)\n  },\n  updateAddTaskOptions ({ commit }, options = {}) {\n    commit('UPDATE_ADD_TASK_OPTIONS', options)\n  },\n  updateInterval ({ commit }, millisecond) {\n    commit('UPDATE_INTERVAL', millisecond)\n  },\n  resetInterval ({ commit }) {\n    commit('UPDATE_INTERVAL', BASE_INTERVAL)\n  },\n  fetchProgress ({ commit }) {\n    api.fetchActiveTaskList()\n      .then((data) => {\n        let progress = -1\n        if (data.length !== 0) {\n          data.forEach((task) => {\n            task.totalLength = Number(task.totalLength)\n            task.completedLength = Number(task.completedLength)\n          })\n          const realTotal = data.reduce((total, task) => total + task.totalLength, 0)\n          if (realTotal === 0) {\n            progress = 2\n          } else {\n            const tasks = data.filter((task) => task.totalLength !== 0)\n            const completed = tasks.reduce((total, task) => total + task.completedLength, 0)\n            const total = tasks.reduce((total, task) => total + task.totalLength, 0)\n            progress = completed / total\n          }\n        }\n        commit('UPDATE_PROGRESS', progress)\n      })\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  getters,\n  mutations,\n  actions\n}\n"
  },
  {
    "path": "src/renderer/store/modules/index.js",
    "content": "/**\n * The file enables `@/store/index.js` to import all vuex modules\n * in a one-shot manner. There should not be any reason to edit this file.\n */\n\nconst files = require.context('.', false, /\\.js$/)\nconst modules = {}\n\nfiles.keys().forEach(key => {\n  if (key === './index.js') return\n  modules[key.replace(/(\\.\\/|\\.js)/g, '')] = files(key).default\n})\n\nexport default modules\n"
  },
  {
    "path": "src/renderer/store/modules/preference.js",
    "content": "import { isEmpty } from 'lodash'\n\nimport api from '@/api'\nimport {\n  getLangDirection,\n  pushItemToFixedLengthArray,\n  removeArrayItem\n} from '@shared/utils'\nimport { fetchBtTrackerFromSource } from '@shared/utils/tracker'\nimport { MAX_NUM_OF_DIRECTORIES } from '@shared/constants'\n\nconst state = {\n  engineMode: 'MAX',\n  config: {}\n}\n\nconst getters = {\n  theme: state => state.config.theme,\n  locale: state => state.config.locale,\n  direction: state => getLangDirection(state.config.locale)\n}\n\nconst mutations = {\n  UPDATE_PREFERENCE_DATA (state, config) {\n    state.config = { ...state.config, ...config }\n  }\n}\n\nconst actions = {\n  fetchPreference ({ dispatch }) {\n    return new Promise((resolve) => {\n      api.fetchPreference()\n        .then((config) => {\n          dispatch('updatePreference', config)\n          resolve(config)\n        })\n    })\n  },\n  save ({ dispatch }, config) {\n    dispatch('task/saveSession', null, { root: true })\n    if (isEmpty(config)) {\n      return\n    }\n\n    dispatch('updatePreference', config)\n    return api.savePreference(config)\n  },\n  recordHistoryDirectory ({ state, dispatch }, directory) {\n    const { historyDirectories = [], favoriteDirectories = [] } = state.config\n    const all = new Set([...historyDirectories, ...favoriteDirectories])\n    if (all.has(directory)) {\n      return\n    }\n\n    dispatch('addHistoryDirectory', directory)\n  },\n  addHistoryDirectory ({ state, dispatch }, directory) {\n    const { historyDirectories = [] } = state.config\n    const history = pushItemToFixedLengthArray(\n      historyDirectories,\n      MAX_NUM_OF_DIRECTORIES,\n      directory\n    )\n\n    dispatch('save', { historyDirectories: history })\n  },\n  favoriteDirectory ({ state, dispatch }, directory) {\n    const { historyDirectories = [], favoriteDirectories = [] } = state.config\n    if (favoriteDirectories.includes(directory) ||\n      favoriteDirectories.length >= MAX_NUM_OF_DIRECTORIES\n    ) {\n      return\n    }\n\n    const favorite = pushItemToFixedLengthArray(\n      favoriteDirectories,\n      MAX_NUM_OF_DIRECTORIES,\n      directory\n    )\n    const history = removeArrayItem(historyDirectories, directory)\n\n    dispatch('save', {\n      historyDirectories: history,\n      favoriteDirectories: favorite\n    })\n  },\n  cancelFavoriteDirectory ({ state, dispatch }, directory) {\n    const { historyDirectories = [], favoriteDirectories = [] } = state.config\n    if (historyDirectories.includes(directory)) {\n      return\n    }\n\n    const favorite = removeArrayItem(favoriteDirectories, directory)\n\n    const history = pushItemToFixedLengthArray(\n      historyDirectories,\n      MAX_NUM_OF_DIRECTORIES,\n      directory\n    )\n\n    dispatch('save', {\n      historyDirectories: history,\n      favoriteDirectories: favorite\n    })\n  },\n  removeDirectory ({ state, dispatch }, directory) {\n    const { historyDirectories = [], favoriteDirectories = [] } = state.config\n\n    const favorite = removeArrayItem(favoriteDirectories, directory)\n    const history = removeArrayItem(historyDirectories, directory)\n\n    dispatch('save', {\n      historyDirectories: history,\n      favoriteDirectories: favorite\n    })\n  },\n  updateAppTheme ({ dispatch }, theme) {\n    dispatch('updatePreference', { theme })\n  },\n  updateAppLocale ({ dispatch }, locale) {\n    dispatch('updatePreference', { locale })\n  },\n  updatePreference  ({ commit }, config) {\n    commit('UPDATE_PREFERENCE_DATA', config)\n  },\n  fetchBtTracker (_, trackerSource = []) {\n    const { proxy = { enable: false } } = state.config\n    console.log('fetchBtTracker', trackerSource, proxy)\n    return fetchBtTrackerFromSource(trackerSource, proxy)\n  },\n  toggleEngineMode () {\n\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  getters,\n  mutations,\n  actions\n}\n"
  },
  {
    "path": "src/renderer/store/modules/task.js",
    "content": "import api from '@/api'\nimport { EMPTY_STRING, TASK_STATUS } from '@shared/constants'\nimport { checkTaskIsBT, intersection } from '@shared/utils'\n\nconst state = {\n  currentList: 'active',\n  taskDetailVisible: false,\n  currentTaskGid: EMPTY_STRING,\n  enabledFetchPeers: false,\n  currentTaskItem: null,\n  currentTaskFiles: [],\n  currentTaskPeers: [],\n  seedingList: [],\n  taskList: [],\n  selectedGidList: []\n}\n\nconst getters = {\n}\n\nconst mutations = {\n  UPDATE_SEEDING_LIST (state, seedingList) {\n    state.seedingList = seedingList\n  },\n  UPDATE_TASK_LIST (state, taskList) {\n    state.taskList = taskList\n  },\n  UPDATE_SELECTED_GID_LIST (state, gidList) {\n    state.selectedGidList = gidList\n  },\n  CHANGE_CURRENT_LIST (state, currentList) {\n    state.currentList = currentList\n  },\n  CHANGE_TASK_DETAIL_VISIBLE (state, visible) {\n    state.taskDetailVisible = visible\n  },\n  UPDATE_CURRENT_TASK_GID (state, gid) {\n    state.currentTaskGid = gid\n  },\n  UPDATE_ENABLED_FETCH_PEERS (state, enabled) {\n    state.enabledFetchPeers = enabled\n  },\n  UPDATE_CURRENT_TASK_ITEM (state, task) {\n    state.currentTaskItem = task\n  },\n  UPDATE_CURRENT_TASK_FILES (state, files) {\n    state.currentTaskFiles = files\n  },\n  UPDATE_CURRENT_TASK_PEERS (state, peers) {\n    state.currentTaskPeers = peers\n  }\n}\n\nconst actions = {\n  changeCurrentList ({ commit, dispatch }, currentList) {\n    commit('CHANGE_CURRENT_LIST', currentList)\n    commit('UPDATE_SELECTED_GID_LIST', [])\n    dispatch('fetchList')\n  },\n  fetchList ({ commit, state }) {\n    return api.fetchTaskList({ type: state.currentList })\n      .then((data) => {\n        commit('UPDATE_TASK_LIST', data)\n\n        const { selectedGidList } = state\n        const gids = data.map((task) => task.gid)\n        const list = intersection(selectedGidList, gids)\n        commit('UPDATE_SELECTED_GID_LIST', list)\n      })\n  },\n  selectTasks ({ commit }, list) {\n    commit('UPDATE_SELECTED_GID_LIST', list)\n  },\n  selectAllTask ({ commit, state }) {\n    const gids = state.taskList.map((task) => task.gid)\n    commit('UPDATE_SELECTED_GID_LIST', gids)\n  },\n  fetchItem ({ dispatch }, gid) {\n    return api.fetchTaskItem({ gid })\n      .then((data) => {\n        dispatch('updateCurrentTaskItem', data)\n      })\n  },\n  fetchItemWithPeers ({ dispatch }, gid) {\n    return api.fetchTaskItemWithPeers({ gid })\n      .then((data) => {\n        console.log('fetchItemWithPeers===>', data)\n        dispatch('updateCurrentTaskItem', data)\n      })\n  },\n  showTaskDetailByGid ({ commit, dispatch }, gid) {\n    api.fetchTaskItem({ gid })\n      .then((task) => {\n        dispatch('updateCurrentTaskItem', task)\n        commit('UPDATE_CURRENT_TASK_GID', task.gid)\n        commit('CHANGE_TASK_DETAIL_VISIBLE', true)\n      })\n  },\n  showTaskDetail ({ commit, dispatch }, task) {\n    dispatch('updateCurrentTaskItem', task)\n    commit('UPDATE_CURRENT_TASK_GID', task.gid)\n    commit('CHANGE_TASK_DETAIL_VISIBLE', true)\n  },\n  hideTaskDetail ({ commit }) {\n    commit('CHANGE_TASK_DETAIL_VISIBLE', false)\n  },\n  toggleEnabledFetchPeers ({ commit }, enabled) {\n    commit('UPDATE_ENABLED_FETCH_PEERS', enabled)\n  },\n  updateCurrentTaskItem ({ commit }, task) {\n    commit('UPDATE_CURRENT_TASK_ITEM', task)\n    if (task) {\n      commit('UPDATE_CURRENT_TASK_FILES', task.files)\n      commit('UPDATE_CURRENT_TASK_PEERS', task.peers)\n    } else {\n      commit('UPDATE_CURRENT_TASK_FILES', [])\n      commit('UPDATE_CURRENT_TASK_PEERS', [])\n    }\n  },\n  updateCurrentTaskGid ({ commit }, gid) {\n    commit('UPDATE_CURRENT_TASK_GID', gid)\n  },\n  addUri ({ dispatch }, data) {\n    const { uris, outs, options } = data\n    return api.addUri({ uris, outs, options })\n      .then(() => {\n        dispatch('fetchList')\n        dispatch('app/updateAddTaskOptions', {}, { root: true })\n      })\n  },\n  addTorrent ({ dispatch }, data) {\n    const { torrent, options } = data\n    return api.addTorrent({ torrent, options })\n      .then(() => {\n        dispatch('fetchList')\n        dispatch('app/updateAddTaskOptions', {}, { root: true })\n      })\n  },\n  addMetalink ({ dispatch }, data) {\n    const { metalink, options } = data\n    return api.addMetalink({ metalink, options })\n      .then(() => {\n        dispatch('fetchList')\n        dispatch('app/updateAddTaskOptions', {}, { root: true })\n      })\n  },\n  getTaskOption (_, gid) {\n    return new Promise((resolve) => {\n      api.getOption({ gid })\n        .then((data) => {\n          resolve(data)\n        })\n    })\n  },\n  changeTaskOption (_, payload) {\n    const { gid, options } = payload\n    return api.changeOption({ gid, options })\n  },\n  removeTask ({ state, dispatch }, task) {\n    const { gid } = task\n    if (gid === state.currentTaskGid) {\n      dispatch('hideTaskDetail')\n    }\n\n    return api.removeTask({ gid })\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  },\n  forcePauseTask ({ dispatch }, task) {\n    const { gid, status } = task\n    if (status !== TASK_STATUS.ACTIVE) {\n      return Promise.resolve(true)\n    }\n\n    return api.forcePauseTask({ gid })\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  },\n  pauseTask ({ dispatch }, task) {\n    const { gid } = task\n    const isBT = checkTaskIsBT(task)\n    const promise = isBT ? api.forcePauseTask({ gid }) : api.pauseTask({ gid })\n    promise.finally(() => {\n      dispatch('fetchList')\n      dispatch('saveSession')\n    })\n    return promise\n  },\n  resumeTask ({ dispatch }, task) {\n    const { gid } = task\n    return api.resumeTask({ gid })\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  },\n  pauseAllTask ({ dispatch }) {\n    return api.pauseAllTask()\n      .catch(() => {\n        return api.forcePauseAllTask()\n      })\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  },\n  resumeAllTask ({ dispatch }) {\n    return api.resumeAllTask()\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  },\n  addToSeedingList ({ state, commit }, gid) {\n    const { seedingList } = state\n    if (seedingList.includes(gid)) {\n      return\n    }\n\n    const list = [\n      ...seedingList,\n      gid\n    ]\n    commit('UPDATE_SEEDING_LIST', list)\n  },\n  removeFromSeedingList ({ state, commit }, gid) {\n    const { seedingList } = state\n    const idx = seedingList.indexOf(gid)\n    if (idx === -1) {\n      return\n    }\n\n    const list = [...seedingList.slice(0, idx), ...seedingList.slice(idx + 1)]\n    commit('UPDATE_SEEDING_LIST', list)\n  },\n  stopSeeding ({ dispatch }, { gid }) {\n    const options = {\n      seedTime: 0\n    }\n    return dispatch('changeTaskOption', { gid, options })\n  },\n  removeTaskRecord ({ state, dispatch }, task) {\n    const { gid, status } = task\n    if (gid === state.currentTaskGid) {\n      dispatch('hideTaskDetail')\n    }\n\n    const { ERROR, COMPLETE, REMOVED } = TASK_STATUS\n    if ([ERROR, COMPLETE, REMOVED].indexOf(status) === -1) {\n      return\n    }\n    return api.removeTaskRecord({ gid })\n      .finally(() => dispatch('fetchList'))\n  },\n  saveSession () {\n    api.saveSession()\n  },\n  purgeTaskRecord ({ dispatch }) {\n    return api.purgeTaskRecord()\n      .finally(() => dispatch('fetchList'))\n  },\n  toggleTask ({ dispatch }, task) {\n    const { status } = task\n    const { ACTIVE, WAITING, PAUSED } = TASK_STATUS\n    if (status === ACTIVE) {\n      return dispatch('pauseTask', task)\n    } else if (status === WAITING || status === PAUSED) {\n      return dispatch('resumeTask', task)\n    }\n  },\n  batchResumeSelectedTasks ({ state }) {\n    const gids = state.selectedGidList\n    if (gids.length === 0) {\n      return\n    }\n\n    return api.batchResumeTask({ gids })\n  },\n  batchPauseSelectedTasks ({ state }) {\n    const gids = state.selectedGidList\n    if (gids.length === 0) {\n      return\n    }\n\n    return api.batchPauseTask({ gids })\n  },\n  batchForcePauseTask (_, gids) {\n    return api.batchForcePauseTask({ gids })\n  },\n  batchResumeTask (_, gids) {\n    return api.batchResumeTask({ gids })\n  },\n  batchRemoveTask ({ dispatch }, gids) {\n    return api.batchRemoveTask({ gids })\n      .finally(() => {\n        dispatch('fetchList')\n        dispatch('saveSession')\n      })\n  }\n}\n\nexport default {\n  namespaced: true,\n  state,\n  getters,\n  mutations,\n  actions\n}\n"
  },
  {
    "path": "src/renderer/utils/native.js",
    "content": "import { access, constants } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { shell, nativeTheme } from '@electron/remote'\nimport { Message } from 'element-ui'\n\nimport {\n  getFileNameFromFile,\n  isMagnetTask\n} from '@shared/utils'\nimport { APP_THEME, TASK_STATUS } from '@shared/constants'\n\nexport const showItemInFolder = (fullPath, { errorMsg }) => {\n  if (!fullPath) {\n    return\n  }\n\n  fullPath = resolve(fullPath)\n  access(fullPath, constants.F_OK, (err) => {\n    console.warn(`[Motrix] ${fullPath} ${err ? 'does not exist' : 'exists'}`)\n    if (err && errorMsg) {\n      Message.error(errorMsg)\n      return\n    }\n\n    shell.showItemInFolder(fullPath)\n  })\n}\n\nexport const openItem = async (fullPath) => {\n  if (!fullPath) {\n    return\n  }\n\n  const result = await shell.openPath(fullPath)\n  return result\n}\n\nexport const getTaskFullPath = (task) => {\n  const { dir, files, bittorrent } = task\n  let result = resolve(dir)\n\n  // Magnet link task\n  if (isMagnetTask(task)) {\n    return result\n  }\n\n  if (bittorrent && bittorrent.info && bittorrent.info.name) {\n    result = resolve(result, bittorrent.info.name)\n    return result\n  }\n\n  const [file] = files\n  const path = file.path ? resolve(file.path) : ''\n  let fileName = ''\n\n  if (path) {\n    result = path\n  } else {\n    if (files && files.length === 1) {\n      fileName = getFileNameFromFile(file)\n      if (fileName) {\n        result = resolve(result, fileName)\n      }\n    }\n  }\n\n  return result\n}\n\nexport const moveTaskFilesToTrash = (task) => {\n  /**\n   * For magnet link tasks, there is bittorrent, but there is no bittorrent.info.\n   * The path is not a complete path before it becomes a BT task.\n   * In order to avoid accidentally deleting the directory\n   * where the task is located, it directly returns true when deleting.\n   */\n  if (isMagnetTask(task)) {\n    return true\n  }\n\n  const { dir, status } = task\n  const path = getTaskFullPath(task)\n  if (!path || dir === path) {\n    throw new Error('task.file-path-error')\n  }\n\n  let deleteResult1 = true\n  access(path, constants.F_OK, async (err) => {\n    console.log(`[Motrix] ${path} ${err ? 'does not exist' : 'exists'}`)\n    if (!err) {\n      deleteResult1 = await shell.trashItem(path)\n    }\n  })\n\n  // There is no configuration file for the completed task.\n  if (status === TASK_STATUS.COMPLETE) {\n    return deleteResult1\n  }\n\n  let deleteResult2 = true\n  const extraFilePath = `${path}.aria2`\n  access(extraFilePath, constants.F_OK, async (err) => {\n    console.log(`[Motrix] ${extraFilePath} ${err ? 'does not exist' : 'exists'}`)\n    if (!err) {\n      deleteResult2 = await shell.trashItem(extraFilePath)\n    }\n  })\n\n  return deleteResult1 && deleteResult2\n}\n\nexport const getSystemTheme = () => {\n  return nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT\n}\n\nexport const delayDeleteTaskFiles = (task, delay) => {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      try {\n        const result = moveTaskFilesToTrash(task)\n        resolve(result)\n      } catch (err) {\n        reject(err.message)\n      }\n    }, delay)\n  })\n}\n"
  },
  {
    "path": "src/renderer/utils/task.js",
    "content": "import { isEmpty } from 'lodash'\n\nimport {\n  ADD_TASK_TYPE,\n  NONE_SELECTED_FILES,\n  SELECTED_ALL_FILES\n} from '@shared/constants'\nimport { splitTaskLinks } from '@shared/utils'\nimport { buildOuts } from '@shared/utils/rename'\n\nimport {\n  buildUrisFromCurl,\n  buildHeadersFromCurl,\n  buildDefaultOptionsFromCurl\n} from '@shared/utils/curl'\n\nexport const initTaskForm = state => {\n  const { addTaskUrl, addTaskOptions } = state.app\n  const {\n    allProxy,\n    dir,\n    engineMaxConnectionPerServer,\n    followMetalink,\n    followTorrent,\n    maxConnectionPerServer,\n    newTaskShowDownloading,\n    split\n  } = state.preference.config\n  const result = {\n    allProxy,\n    cookie: '',\n    dir,\n    engineMaxConnectionPerServer,\n    followMetalink,\n    followTorrent,\n    maxConnectionPerServer,\n    newTaskShowDownloading,\n    out: '',\n    referer: '',\n    selectFile: NONE_SELECTED_FILES,\n    split,\n    torrent: '',\n    uris: addTaskUrl,\n    userAgent: '',\n    authorization: '',\n    ...addTaskOptions\n  }\n  return result\n}\n\nexport const buildHeader = (form) => {\n  const { userAgent, referer, cookie, authorization } = form\n  const result = []\n\n  if (!isEmpty(userAgent)) {\n    result.push(`User-Agent: ${userAgent}`)\n  }\n  if (!isEmpty(referer)) {\n    result.push(`Referer: ${referer}`)\n  }\n  if (!isEmpty(cookie)) {\n    result.push(`Cookie: ${cookie}`)\n  }\n  if (!isEmpty(authorization)) {\n    result.push(`Authorization: ${authorization}`)\n  }\n\n  return result\n}\n\nexport const buildOption = (type, form) => {\n  const {\n    allProxy,\n    dir,\n    out,\n    selectFile,\n    split\n  } = form\n  const result = {}\n\n  if (!isEmpty(allProxy)) {\n    result.allProxy = allProxy\n  }\n\n  if (!isEmpty(dir)) {\n    result.dir = dir\n  }\n\n  if (!isEmpty(out)) {\n    result.out = out\n  }\n\n  if (split > 0) {\n    result.split = split\n  }\n\n  if (type === ADD_TASK_TYPE.TORRENT) {\n    if (\n      selectFile !== SELECTED_ALL_FILES &&\n      selectFile !== NONE_SELECTED_FILES\n    ) {\n      result.selectFile = selectFile\n    }\n  }\n\n  const header = buildHeader(form)\n  if (!isEmpty(header)) {\n    result.header = header\n  }\n\n  return result\n}\n\nexport const buildUriPayload = (form) => {\n  let { uris, out } = form\n  if (isEmpty(uris)) {\n    throw new Error('task.new-task-uris-required')\n  }\n\n  uris = splitTaskLinks(uris)\n  const curlHeaders = buildHeadersFromCurl(uris)\n  uris = buildUrisFromCurl(uris)\n  const outs = buildOuts(uris, out)\n\n  form = buildDefaultOptionsFromCurl(form, curlHeaders)\n\n  const options = buildOption(ADD_TASK_TYPE.URI, form)\n  const result = {\n    uris,\n    outs,\n    options\n  }\n  return result\n}\n\nexport const buildTorrentPayload = (form) => {\n  const { torrent } = form\n  if (isEmpty(torrent)) {\n    throw new Error('task.new-task-torrent-required')\n  }\n\n  const options = buildOption(ADD_TASK_TYPE.TORRENT, form)\n  const result = {\n    torrent,\n    options\n  }\n  return result\n}\n"
  },
  {
    "path": "src/renderer/workers/tray.worker.js",
    "content": "/* eslint no-unused-vars: 'off' */\nimport { TRAY_CANVAS_CONFIG } from '@shared/constants'\nimport { draw } from '@shared/utils/tray'\n\nlet idx = 0\nlet canvas\n\nconst initCanvas = () => {\n  if (canvas) {\n    return canvas\n  }\n\n  const { WIDTH, HEIGHT } = TRAY_CANVAS_CONFIG\n  return new OffscreenCanvas(WIDTH, HEIGHT)\n}\n\nconst drawTray = async (payload) => {\n  self.postMessage({\n    type: 'log',\n    payload\n  })\n\n  if (!canvas) {\n    canvas = initCanvas()\n  }\n\n  try {\n    const tray = await draw({\n      canvas,\n      ...payload\n    })\n\n    self.postMessage({\n      type: 'tray:drawed',\n      payload: {\n        idx,\n        tray\n      }\n    })\n\n    idx += 1\n  } catch (error) {\n    logger(error.message)\n  }\n}\n\nconst logger = (text) => {\n  self.postMessage({\n    type: 'log',\n    payload: text\n  })\n}\n\nself.postMessage({\n  type: 'initialized',\n  payload: Date.now()\n})\n\nself.addEventListener('message', (event) => {\n  const { type, payload } = event.data\n  switch (type) {\n  case 'tray:draw':\n    drawTray(payload)\n    break\n  default:\n    logger(JSON.stringify(event.data))\n  }\n})\n"
  },
  {
    "path": "src/shared/aria2/index.js",
    "content": "'use strict'\n\nconst Aria2 = require('./lib/Aria2')\n\nmodule.exports = Aria2\n"
  },
  {
    "path": "src/shared/aria2/lib/Aria2.js",
    "content": "'use strict'\n\nimport { JSONRPCClient } from './JSONRPCClient'\n\nexport class Aria2 extends JSONRPCClient {\n  prefix (str) {\n    if (!str.startsWith('system.') && !str.startsWith('aria2.')) {\n      str = 'aria2.' + str\n    }\n    return str\n  }\n\n  unprefix (str) {\n    const suffix = str.split('aria2.')[1]\n    return suffix || str\n  }\n\n  addSecret (parameters) {\n    let params = this.secret ? ['token:' + this.secret] : []\n    if (Array.isArray(parameters)) {\n      params = params.concat(parameters)\n    }\n    return params\n  }\n\n  _onnotification (notification) {\n    const { method, params } = notification\n    const event = this.unprefix(method)\n    if (event !== method) this.emit(event, params)\n    return super._onnotification(notification)\n  }\n\n  async call (method, ...params) {\n    return super.call(this.prefix(method), this.addSecret(params))\n  }\n\n  async multicall (calls) {\n    const multi = [\n      calls.map(([method, ...params]) => {\n        return { methodName: this.prefix(method), params: this.addSecret(params) }\n      })\n    ]\n    return super.call('system.multicall', multi)\n  }\n\n  async batch (calls) {\n    return super.batch(\n      calls.map(([method, ...params]) => [\n        this.prefix(method),\n        this.addSecret(params)\n      ])\n    )\n  }\n\n  async listNotifications () {\n    const events = await this.call('system.listNotifications')\n    return events.map((event) => this.unprefix(event))\n  }\n\n  async listMethods () {\n    const methods = await this.call('system.listMethods')\n    return methods.map((method) => this.unprefix(method))\n  }\n\n  defaultOptions = Object.assign({}, JSONRPCClient.defaultOptions, {\n    secure: false,\n    host: 'localhost',\n    port: 16800,\n    secret: '',\n    path: '/jsonrpc'\n  })\n}\n"
  },
  {
    "path": "src/shared/aria2/lib/Deferred.js",
    "content": "'use strict'\n\nmodule.exports = function Deferred () {\n  this.promise = new Promise((resolve, reject) => {\n    this.resolve = resolve\n    this.reject = reject\n  })\n}\n"
  },
  {
    "path": "src/shared/aria2/lib/JSONRPCClient.js",
    "content": "'use strict'\n\nimport { EventEmitter } from 'node:events'\nimport _fetch from 'node-fetch'\nimport _WebSocket from 'ws'\nimport { JSONRPCError } from './JSONRPCError'\n\nconst Deferred = require('./Deferred')\nconst promiseEvent = require('./promiseEvent')\n\nconst WebSocket = global.WebSocket || _WebSocket\nconst fetch = global.fetch ? global.fetch.bind(global) : _fetch\n\nexport class JSONRPCClient extends EventEmitter {\n  constructor (options) {\n    super()\n    this.deferreds = Object.create(null)\n    this.lastId = 0\n\n    Object.assign(this, this.defaultOptions, options)\n  }\n\n  id () {\n    return this.lastId++\n  }\n\n  url (protocol) {\n    return (\n      protocol +\n      (this.secure ? 's' : '') +\n      '://' +\n      this.host +\n      ':' +\n      this.port +\n      this.path\n    )\n  }\n\n  websocket (message) {\n    return new Promise((resolve, reject) => {\n      const cb = (err) => {\n        if (err) reject(err)\n        else resolve()\n      }\n      this.socket.send(JSON.stringify(message), cb)\n      if (global.WebSocket && this.socket instanceof global.WebSocket) cb()\n    })\n  }\n\n  async http (message) {\n    const response = await fetch(this.url('http'), {\n      method: 'POST',\n      body: JSON.stringify(message),\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json'\n      }\n    })\n\n    response\n      .json()\n      .then(this._onmessage)\n      .catch((err) => {\n        this.emit('error', err)\n      })\n\n    return response\n  }\n\n  _buildMessage (method, params) {\n    if (typeof method !== 'string') {\n      throw new TypeError(method + ' is not a string')\n    }\n\n    const message = {\n      method,\n      'json-rpc': '2.0',\n      id: this.id()\n    }\n\n    if (params) Object.assign(message, { params })\n    return message\n  }\n\n  async batch (calls) {\n    const message = calls.map(([method, params]) => {\n      return this._buildMessage(method, params)\n    })\n\n    await this._send(message)\n\n    return message.map(({ id }) => {\n      const { promise } = (this.deferreds[id] = new Deferred())\n      return promise\n    })\n  }\n\n  async call (method, parameters) {\n    const message = this._buildMessage(method, parameters)\n    await this._send(message)\n\n    const { promise } = (this.deferreds[message.id] = new Deferred())\n\n    return promise\n  }\n\n  async _send (message) {\n    this.emit('output', message)\n\n    const { socket } = this\n    return socket && socket.readyState === 1\n      ? this.websocket(message)\n      : this.http(message)\n  }\n\n  _onresponse ({ id, error, result }) {\n    const deferred = this.deferreds[id]\n    if (!deferred) return\n    if (error) deferred.reject(new JSONRPCError(error))\n    else deferred.resolve(result)\n    delete this.deferreds[id]\n  }\n\n  _onrequest ({ method, params }) {\n    return this.onrequest(method, params)\n  }\n\n  _onnotification ({ method, params }) {\n    this.emit(method, params)\n  }\n\n  _onmessage = (message) => {\n    this.emit('input', message)\n\n    if (Array.isArray(message)) {\n      for (const object of message) {\n        this._onobject(object)\n      }\n    } else {\n      this._onobject(message)\n    }\n  }\n\n  _onobject (message) {\n    if (message.method === undefined) this._onresponse(message)\n    else if (message.id === undefined) this._onnotification(message)\n    else this._onrequest(message)\n  }\n\n  async open () {\n    const socket = (this.socket = new WebSocket(this.url('ws')))\n\n    socket.onclose = (...args) => {\n      this.emit('close', ...args)\n    }\n    socket.onmessage = (event) => {\n      let message\n      try {\n        message = JSON.parse(event.data)\n      } catch (err) {\n        this.emit('error', err)\n        return\n      }\n      this._onmessage(message)\n    }\n    socket.onopen = (...args) => {\n      this.emit('open', ...args)\n    }\n    socket.onerror = (...args) => {\n      this.emit('error', ...args)\n    }\n\n    return promiseEvent(this, 'open')\n  }\n\n  async close () {\n    const { socket } = this\n    socket.close()\n    return promiseEvent(this, 'close')\n  }\n\n  defaultOptions = {\n    secure: false,\n    host: 'localhost',\n    port: 80,\n    secret: '',\n    path: '/jsonrpc',\n    fetch,\n    WebSocket\n  }\n}\n"
  },
  {
    "path": "src/shared/aria2/lib/JSONRPCError.js",
    "content": "'use strict'\n\nexport class JSONRPCError extends Error {\n  constructor ({ message, code, data }) {\n    super(message)\n    this.code = code\n    if (data) this.data = data\n    this.name = this.constructor.name\n  }\n}\n"
  },
  {
    "path": "src/shared/aria2/lib/debug.js",
    "content": "'use strict'\n\nimport { inspect } from 'util'\n\nmodule.exports = (aria2) => {\n  aria2.on('open', () => {\n    console.log('aria2', 'OPEN')\n  })\n\n  aria2.on('close', () => {\n    console.log('aria2', 'CLOSE')\n  })\n\n  aria2.on('input', (m) => {\n    console.log('aria2', 'IN')\n    console.log(inspect(m, { depth: null, colors: true }))\n  })\n\n  aria2.on('output', (m) => {\n    console.log('aria2', 'OUT')\n    console.log(inspect(m, { depth: null, colors: true }))\n  })\n}\n"
  },
  {
    "path": "src/shared/aria2/lib/promiseEvent.js",
    "content": "'use strict'\n\nmodule.exports = function promiseEvent (target, event) {\n  return new Promise((resolve, reject) => {\n    function cleanup () {\n      target.removeListener(event, onEvent)\n      target.removeListener('error', onError)\n    }\n    function onEvent (data) {\n      resolve(data)\n      cleanup()\n    }\n    function onError (err) {\n      reject(err)\n      cleanup()\n    }\n    target.addListener(event, onEvent)\n    target.addListener('error', onError)\n  })\n}\n"
  },
  {
    "path": "src/shared/colors.json",
    "content": "{\n  \"active\": \"#5b5bea\",\n  \"waiting\": \"#737373\",\n  \"paused\": \"#737373\",\n  \"error\": \"#FF6157\",\n  \"complete\": \"#2ACB42\",\n  \"removed\": \"#737373\",\n  \"seeding\": \"#2ACB42\"\n}\n"
  },
  {
    "path": "src/shared/configKeys.js",
    "content": "const userKeys = [\n  'auto-check-update',\n  'auto-hide-window',\n  'auto-sync-tracker',\n  'cookie',\n  'enable-upnp',\n  'engine-bin-path',\n  'engine-max-connection-per-server',\n  'favorite-directories',\n  'hide-app-menu',\n  'history-directories',\n  'keep-seeding',\n  'keep-window-state',\n  'last-check-update-time',\n  'last-sync-tracker-time',\n  'locale',\n  'log-level',\n  'new-task-show-downloading',\n  'no-confirm-before-delete-task',\n  'open-at-login',\n  'protocols',\n  'proxy',\n  'resume-all-when-app-launched',\n  'run-mode',\n  'show-progress-bar',\n  'task-notification',\n  'theme',\n  'tracker-source',\n  'tray-speedometer'\n]\n\nconst systemKeys = [\n  'all-proxy-passwd',\n  'all-proxy-user',\n  'all-proxy',\n  'allow-overwrite',\n  'allow-piece-length-change',\n  'always-resume',\n  'async-dns',\n  'auto-file-renaming',\n  'bt-enable-hook-after-hash-check',\n  'bt-enable-lpd',\n  'bt-exclude-tracker',\n  'bt-external-ip',\n  'bt-force-encryption',\n  'bt-hash-check-seed',\n  'bt-load-saved-metadata',\n  'bt-max-peers',\n  'bt-metadata-only',\n  'bt-min-crypto-level',\n  'bt-prioritize-piece',\n  'bt-remove-unselected-file',\n  'bt-request-peer-speed-limit',\n  'bt-require-crypto',\n  'bt-save-metadata',\n  'bt-seed-unverified',\n  'bt-stop-timeout',\n  'bt-tracker-connect-timeout',\n  'bt-tracker-interval',\n  'bt-tracker-timeout',\n  'bt-tracker',\n  'check-integrity',\n  'checksum',\n  'conditional-get',\n  'connect-timeout',\n  'content-disposition-default-utf8',\n  'continue',\n  'dht-file-path',\n  'dht-file-path6',\n  'dht-listen-port',\n  'dir',\n  'dry-run',\n  'enable-http-keep-alive',\n  'enable-http-pipelining',\n  'enable-mmap',\n  'enable-peer-exchange',\n  'file-allocation',\n  'follow-metalink',\n  'follow-torrent',\n  'force-save',\n  'force-sequential',\n  'ftp-passwd',\n  'ftp-pasv',\n  'ftp-proxy-passwd',\n  'ftp-proxy-user',\n  'ftp-proxy',\n  'ftp-reuse-connection',\n  'ftp-type',\n  'ftp-user',\n  'gid',\n  'hash-check-only',\n  'header',\n  'http-accept-gzip',\n  'http-auth-challenge',\n  'http-no-cache',\n  'http-passwd',\n  'http-proxy-passwd',\n  'http-proxy-user',\n  'http-proxy',\n  'http-user',\n  'https-proxy-passwd',\n  'https-proxy-user',\n  'https-proxy',\n  'index-out',\n  'listen-port',\n  'lowest-speed-limit',\n  'max-concurrent-downloads',\n  'max-connection-per-server',\n  'max-download-limit',\n  'max-file-not-found',\n  'max-mmap-limit',\n  'max-overall-download-limit',\n  'max-overall-upload-limit',\n  'max-resume-failure-tries',\n  'max-tries',\n  'max-upload-limit',\n  'metalink-base-uri',\n  'metalink-enable-unique-protocol',\n  'metalink-language',\n  'metalink-location',\n  'metalink-os',\n  'metalink-preferred-protocol',\n  'metalink-version',\n  'min-split-size',\n  'no-file-allocation-limit',\n  'no-netrc',\n  'no-proxy',\n  'no-want-digest-header',\n  'out',\n  'parameterized-uri',\n  'pause-metadata',\n  'pause',\n  'piece-length',\n  'proxy-method',\n  'realtime-chunk-checksum',\n  'referer',\n  'remote-time',\n  'remove-control-file',\n  'retry-wait',\n  'reuse-uri',\n  'rpc-listen-port',\n  'rpc-save-upload-metadata',\n  'rpc-secret',\n  'seed-ratio',\n  'seed-time',\n  'select-file',\n  'split',\n  'ssh-host-key-md',\n  'stream-piece-selector',\n  'timeout',\n  'uri-selector',\n  'use-head',\n  'user-agent'\n]\n\nconst needRestartKeys = [\n  'dht-listen-port',\n  'hide-app-menu',\n  'listen-port',\n  'rpc-listen-port',\n  'rpc-secret'\n]\n\nexport {\n  userKeys,\n  systemKeys,\n  needRestartKeys\n}\n"
  },
  {
    "path": "src/shared/constants.js",
    "content": "export const EMPTY_STRING = ''\nexport const PORTABLE_EXECUTABLE_DIR = process.env.PORTABLE_EXECUTABLE_DIR\nexport const IS_PORTABLE = PORTABLE_EXECUTABLE_DIR && PORTABLE_EXECUTABLE_DIR !== EMPTY_STRING\n\nexport const APP_THEME = {\n  AUTO: 'auto',\n  LIGHT: 'light',\n  DARK: 'dark'\n}\n\nexport const APP_RUN_MODE = {\n  STANDARD: 1,\n  TRAY: 2,\n  HIDE_TRAY: 3\n}\n\nexport const ADD_TASK_TYPE = {\n  URI: 'uri',\n  TORRENT: 'torrent'\n}\n\nexport const TASK_STATUS = {\n  ACTIVE: 'active',\n  WAITING: 'waiting',\n  PAUSED: 'paused',\n  ERROR: 'error',\n  COMPLETE: 'complete',\n  REMOVED: 'removed',\n  SEEDING: 'seeding'\n}\n\nexport const LOG_LEVELS = [\n  'error',\n  'warn',\n  'info',\n  'verbose',\n  'debug',\n  'silly'\n]\n\nexport const MAX_NUM_OF_DIRECTORIES = 5\n\nexport const ENGINE_RPC_HOST = '127.0.0.1'\nexport const ENGINE_RPC_PORT = 16800\nexport const ENGINE_MAX_CONCURRENT_DOWNLOADS = 10\nexport const ENGINE_MAX_CONNECTION_PER_SERVER = 64\n\nexport const UNKNOWN_PEERID = '%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00'\nexport const UNKNOWN_PEERID_NAME = 'unknown'\nexport const GRAPHIC = '░▒▓█'\n\nexport const ONE_SECOND = 1000\nexport const ONE_MINUTE = ONE_SECOND * 60\nexport const ONE_HOUR = ONE_MINUTE * 60\nexport const ONE_DAY = ONE_HOUR * 24\n\n// 12 Hours\nexport const AUTO_SYNC_TRACKER_INTERVAL = ONE_HOUR * 12\n\n// One Week\nexport const AUTO_CHECK_UPDATE_INTERVAL = ONE_DAY * 7\n\nexport const MAX_BT_TRACKER_LENGTH = 6144\n\n/**\n * @see https://github.com/ngosang/trackerslist\n */\nexport const NGOSANG_TRACKERS_BEST_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt'\nexport const NGOSANG_TRACKERS_BEST_IP_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best_ip.txt'\nexport const NGOSANG_TRACKERS_ALL_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt'\nexport const NGOSANG_TRACKERS_ALL_IP_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_ip.txt'\n\nexport const NGOSANG_TRACKERS_BEST_URL_CDN = 'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_best.txt'\nexport const NGOSANG_TRACKERS_BEST_IP_URL_CDN = 'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_best_ip.txt'\nexport const NGOSANG_TRACKERS_ALL_URL_CDN = 'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt'\nexport const NGOSANG_TRACKERS_ALL_IP_URL_CDN = 'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all_ip.txt'\n\n/**\n * @see https://github.com/XIU2/TrackersListCollection\n */\nexport const XIU2_TRACKERS_BEST_URL = 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/best.txt'\nexport const XIU2_TRACKERS_ALL_URL = 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt'\nexport const XIU2_TRACKERS_HTTP_URL = 'https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/http.txt'\n\nexport const XIU2_TRACKERS_BEST_URL_CDN = 'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection/best.txt'\nexport const XIU2_TRACKERS_ALL_URL_CDN = 'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection/all.txt'\nexport const XIU2_TRACKERS_HTTP_URL_CDN = 'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection/http.txt'\n\n// For bt-exclude-tracker\nexport const XIU2_TRACKERS_BLACK_URL = 'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection/blacklist.txt'\n\nexport const TRACKER_SOURCE_OPTIONS = [\n  {\n    label: 'ngosang/trackerslist',\n    options: [\n      {\n        value: NGOSANG_TRACKERS_BEST_URL,\n        label: 'trackers_best.txt',\n        cdn: false\n      },\n      {\n        value: NGOSANG_TRACKERS_BEST_IP_URL,\n        label: 'trackers_best_ip.txt',\n        cdn: false\n      },\n      {\n        value: NGOSANG_TRACKERS_ALL_URL,\n        label: 'trackers_all.txt',\n        cdn: false\n      },\n      {\n        value: NGOSANG_TRACKERS_ALL_IP_URL,\n        label: 'trackers_all_ip.txt',\n        cdn: false\n      },\n      {\n        value: NGOSANG_TRACKERS_BEST_URL_CDN,\n        label: 'trackers_best.txt',\n        cdn: true\n      },\n      {\n        value: NGOSANG_TRACKERS_BEST_IP_URL_CDN,\n        label: 'trackers_best_ip.txt',\n        cdn: true\n      },\n      {\n        value: NGOSANG_TRACKERS_ALL_URL_CDN,\n        label: 'trackers_all.txt',\n        cdn: true\n      },\n      {\n        value: NGOSANG_TRACKERS_ALL_IP_URL_CDN,\n        label: 'trackers_all_ip.txt',\n        cdn: true\n      }\n    ]\n  },\n  {\n    label: 'XIU2/TrackersListCollection',\n    options: [\n      {\n        value: XIU2_TRACKERS_BEST_URL,\n        label: 'best.txt',\n        cdn: false\n      },\n      {\n        value: XIU2_TRACKERS_ALL_URL,\n        label: 'all.txt',\n        cdn: false\n      },\n      {\n        value: XIU2_TRACKERS_HTTP_URL,\n        label: 'http.txt',\n        cdn: false\n      },\n      {\n        value: XIU2_TRACKERS_BEST_URL_CDN,\n        label: 'best.txt',\n        cdn: true\n      },\n      {\n        value: XIU2_TRACKERS_ALL_URL_CDN,\n        label: 'all.txt',\n        cdn: true\n      },\n      {\n        value: XIU2_TRACKERS_HTTP_URL_CDN,\n        label: 'http.txt',\n        cdn: true\n      }\n    ]\n  }\n]\n\nexport const PROXY_SCOPES = {\n  DOWNLOAD: 'download',\n  UPDATE_APP: 'update-app',\n  UPDATE_TRACKERS: 'update-trackers'\n}\n\nexport const PROXY_SCOPE_OPTIONS = [\n  PROXY_SCOPES.DOWNLOAD,\n  PROXY_SCOPES.UPDATE_APP,\n  PROXY_SCOPES.UPDATE_TRACKERS\n]\n\nexport const NONE_SELECTED_FILES = 'none'\nexport const SELECTED_ALL_FILES = 'all'\n\nexport const IP_VERSION = {\n  V4: 4,\n  V6: 6\n}\n\nexport const LOGIN_SETTING_OPTIONS = {\n  // For Windows\n  args: [\n    '--opened-at-login=1'\n  ]\n}\n\nexport const TRAY_CANVAS_CONFIG = {\n  WIDTH: 66,\n  HEIGHT: 16,\n  ICON_WIDTH: 16,\n  ICON_HEIGHT: 16,\n  TEXT_WIDTH: 46,\n  TEXT_FONT_SIZE: 8\n}\n\nexport const COMMON_RESOURCE_TAGS = ['http://', 'https://', 'ftp://', 'magnet:']\nexport const THUNDER_RESOURCE_TAGS = ['thunder://']\n\nexport const RESOURCE_TAGS = [\n  ...COMMON_RESOURCE_TAGS,\n  ...THUNDER_RESOURCE_TAGS\n]\n\nexport const SUPPORT_RTL_LOCALES = [\n  /* 'العربية', Arabic */\n  'ar',\n  /* 'فارسی', Persian */\n  'fa',\n  /* 'עברית', Hebrew */\n  'he',\n  /* 'Kurdî / كوردی', Kurdish */\n  'ku',\n  /* 'پنجابی', Western Punjabi */\n  'pa',\n  /* 'پښتو', Pashto, */\n  'ps',\n  /* 'سنڌي', Sindhi */\n  'sd',\n  /* 'اردو', Urdu */\n  'ur',\n  /* 'ייִדיש', Yiddish */\n  'yi'\n]\n\nexport const IMAGE_SUFFIXES = [\n  '.ai',\n  '.bmp',\n  '.eps',\n  '.fig',\n  '.gif',\n  '.heic',\n  '.icn',\n  '.ico',\n  '.jpeg',\n  '.jpg',\n  '.png',\n  '.psd',\n  '.raw',\n  '.sketch',\n  '.svg',\n  '.tif',\n  '.webp',\n  '.xd'\n]\n\nexport const AUDIO_SUFFIXES = [\n  '.aac',\n  '.ape',\n  '.flac',\n  '.flav',\n  '.m4a',\n  '.mp3',\n  '.ogg',\n  '.wav',\n  '.wma'\n]\n\nexport const VIDEO_SUFFIXES = [\n  '.avi',\n  '.m4v',\n  '.mkv',\n  '.mov',\n  '.mp4',\n  '.mpg',\n  '.rmvb',\n  '.vob',\n  '.wmv'\n]\n\nexport const SUB_SUFFIXES = [\n  '.ass',\n  '.idx',\n  '.smi',\n  '.srt',\n  '.ssa',\n  '.sst',\n  '.sub'\n]\n\nexport const DOCUMENT_SUFFIXES = [\n  '.azw3',\n  '.csv',\n  '.doc',\n  '.docx',\n  '.epub',\n  '.key',\n  '.mobi',\n  '.numbers',\n  '.pages',\n  '.pdf',\n  '.ppt',\n  '.pptx',\n  '.txt',\n  '.xsl',\n  '.xslx'\n]\n"
  },
  {
    "path": "src/shared/keymap.json",
    "content": "{\n  \"cmdctrl-q\": \"application:quit\",\n  \"cmdctrl-n\": \"application:new-task\",\n  \"cmdctrl-shift-n\": \"application:new-bt-task\",\n  \"cmdctrl-o\": \"application:open-file\",\n  \"cmdctrl-l\": \"application:task-list\",\n  \"cmdctrl-,\": \"application:preferences\",\n  \"cmdctrl-shift-p\": \"application:pause-all-task\",\n  \"cmdctrl-shift-r\": \"application:resume-all-task\",\n  \"ctrl-shift-a\": \"application:select-all-task\"\n}\n"
  },
  {
    "path": "src/shared/locales/LocaleManager.js",
    "content": "import i18next from 'i18next'\nimport { getLanguage } from '@shared/locales'\n\nexport default class LocaleManager {\n  constructor (options = {}) {\n    this.options = options\n\n    i18next.init({\n      fallbackLng: 'en-US',\n      resources: options.resources\n    })\n  }\n\n  changeLanguage (lng) {\n    return i18next.changeLanguage(lng)\n  }\n\n  changeLanguageByLocale (locale) {\n    const lng = getLanguage(locale)\n    return this.changeLanguage(lng)\n  }\n\n  getI18n () {\n    return i18next\n  }\n}\n"
  },
  {
    "path": "src/shared/locales/all.js",
    "content": "import eleLocaleAr from 'element-ui/lib/locale/lang/ar'\nimport eleLocaleBg from 'element-ui/lib/locale/lang/bg'\nimport eleLocaleCa from 'element-ui/lib/locale/lang/ca'\nimport eleLocaleDe from 'element-ui/lib/locale/lang/de'\nimport eleLocaleEl from 'element-ui/lib/locale/lang/el'\nimport eleLocaleEn from 'element-ui/lib/locale/lang/en'\nimport eleLocaleEs from 'element-ui/lib/locale/lang/es'\nimport eleLocaleFa from 'element-ui/lib/locale/lang/fa'\nimport eleLocaleFr from 'element-ui/lib/locale/lang/fr'\nimport eleLocaleHu from 'element-ui/lib/locale/lang/hu'\nimport eleLocaleId from 'element-ui/lib/locale/lang/id'\nimport elelocaleIt from 'element-ui/lib/locale/lang/it'\nimport eleLocaleJa from 'element-ui/lib/locale/lang/ja'\nimport eleLocaleKo from 'element-ui/lib/locale/lang/ko'\nimport eleLocaleNb from 'element-ui/lib/locale/lang/nb-NO'\nimport eleLocaleNl from 'element-ui/lib/locale/lang/nl'\nimport eleLocalePl from 'element-ui/lib/locale/lang/pl'\nimport eleLocalePtBR from 'element-ui/lib/locale/lang/pt-br'\nimport eleLocaleRo from 'element-ui/lib/locale/lang/ro'\nimport eleLocaleRu from 'element-ui/lib/locale/lang/ru-RU'\nimport eleLocaleTh from 'element-ui/lib/locale/lang/th'\nimport eleLocaleTr from 'element-ui/lib/locale/lang/tr-TR'\nimport eleLocaleUk from 'element-ui/lib/locale/lang/ua'\nimport eleLocaleVi from 'element-ui/lib/locale/lang/vi'\nimport eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'\nimport eleLocaleZhTW from 'element-ui/lib/locale/lang/zh-TW'\nimport appLocaleAr from '@shared/locales/ar'\nimport appLocaleBg from '@shared/locales/bg'\nimport appLocaleCa from '@shared/locales/ca'\nimport appLocaleDe from '@shared/locales/de'\nimport appLocaleEl from '@shared/locales/el'\nimport appLocaleEnUS from '@shared/locales/en-US'\nimport appLocaleEs from '@shared/locales/es'\nimport appLocaleFa from '@shared/locales/fa'\nimport appLocaleFr from '@shared/locales/fr'\nimport appLocaleHu from '@shared/locales/hu'\nimport appLocaleId from '@shared/locales/id'\nimport applocaleIt from '@shared/locales/it'\nimport appLocaleJa from '@shared/locales/ja'\nimport appLocaleKo from '@shared/locales/ko'\nimport appLocaleNb from '@shared/locales/nb'\nimport appLocaleNl from '@shared/locales/nl'\nimport appLocalePl from '@shared/locales/pl'\nimport appLocalePtBR from '@shared/locales/pt-BR'\nimport appLocaleRo from '@shared/locales/ro'\nimport appLocaleRu from '@shared/locales/ru'\nimport appLocaleTh from '@shared/locales/th'\nimport appLocaleTr from '@shared/locales/tr'\nimport appLocaleUk from '@shared/locales/uk'\nimport appLocaleVi from '@shared/locales/vi'\nimport appLocaleZhCN from '@shared/locales/zh-CN'\nimport appLocaleZhTW from '@shared/locales/zh-TW'\n\n// Please keep the locale key in alphabetical order.\n/* eslint-disable quote-props */\nconst resources = {\n  'ar': {\n    translation: {\n      ...eleLocaleAr,\n      ...appLocaleAr\n    }\n  },\n  'bg': {\n    translation: {\n      ...eleLocaleBg,\n      ...appLocaleBg\n    }\n  },\n  'ca': {\n    translation: {\n      ...eleLocaleCa,\n      ...appLocaleCa\n    }\n  },\n  'de': {\n    translation: {\n      ...eleLocaleDe,\n      ...appLocaleDe\n    }\n  },\n  'el': {\n    translation: {\n      ...eleLocaleEl,\n      ...appLocaleEl\n    }\n  },\n  'en-US': {\n    translation: {\n      ...eleLocaleEn,\n      ...appLocaleEnUS\n    }\n  },\n  'es': {\n    translation: {\n      ...eleLocaleEs,\n      ...appLocaleEs\n    }\n  },\n  'fa': {\n    translation: {\n      ...eleLocaleFa,\n      ...appLocaleFa\n    }\n  },\n  'fr': {\n    translation: {\n      ...eleLocaleFr,\n      ...appLocaleFr\n    }\n  },\n  'hu': {\n    translation: {\n      ...eleLocaleHu,\n      ...appLocaleHu\n    }\n  },\n  'id': {\n    translation: {\n      ...eleLocaleId,\n      ...appLocaleId\n    }\n  },\n  'it': {\n    translation: {\n      ...elelocaleIt,\n      ...applocaleIt\n    }\n  },\n  'ja': {\n    translation: {\n      ...eleLocaleJa,\n      ...appLocaleJa\n    }\n  },\n  'ko': {\n    translation: {\n      ...eleLocaleKo,\n      ...appLocaleKo\n    }\n  },\n  'nb': {\n    translation: {\n      ...eleLocaleNb,\n      ...appLocaleNb\n    }\n  },\n  'nl': {\n    translation: {\n      ...eleLocaleNl,\n      ...appLocaleNl\n    }\n  },\n  'pl': {\n    translation: {\n      ...eleLocalePl,\n      ...appLocalePl\n    }\n  },\n  'pt-BR': {\n    translation: {\n      ...eleLocalePtBR,\n      ...appLocalePtBR\n    }\n  },\n  'ro': {\n    translation: {\n      ...eleLocaleRo,\n      ...appLocaleRo\n    }\n  },\n  'ru': {\n    translation: {\n      ...eleLocaleRu,\n      ...appLocaleRu\n    }\n  },\n  'th': {\n    translation: {\n      ...eleLocaleTh,\n      ...appLocaleTh\n    }\n  },\n  'tr': {\n    translation: {\n      ...eleLocaleTr,\n      ...appLocaleTr\n    }\n  },\n  'uk': {\n    translation: {\n      ...eleLocaleUk,\n      ...appLocaleUk\n    }\n  },\n  'vi': {\n    translation: {\n      ...eleLocaleVi,\n      ...appLocaleVi\n    }\n  },\n  'zh-CN': {\n    translation: {\n      ...eleLocaleZhCN,\n      ...appLocaleZhCN\n    }\n  },\n  'zh-TW': {\n    translation: {\n      ...eleLocaleZhTW,\n      ...appLocaleZhTW\n    }\n  }\n}\n/* eslint-enable quote-props */\n\nexport default resources\n"
  },
  {
    "path": "src/shared/locales/app.js",
    "content": "import appLocaleAr from '@shared/locales/ar'\nimport appLocaleBg from '@shared/locales/bg'\nimport appLocaleCa from '@shared/locales/ca'\nimport appLocaleDe from '@shared/locales/de'\nimport appLocaleEl from '@shared/locales/el'\nimport appLocaleEnUS from '@shared/locales/en-US'\nimport appLocaleFa from '@shared/locales/fa'\nimport appLocaleFr from '@shared/locales/fr'\nimport appLocaleHu from '@shared/locales/hu'\nimport appLocaleId from '@shared/locales/id'\nimport appLocaleIt from '@shared/locales/it'\nimport appLocaleJa from '@shared/locales/ja'\nimport appLocaleNl from '@shared/locales/nl'\nimport appLocaleKo from '@shared/locales/ko'\nimport appLocalePl from '@shared/locales/pl'\nimport appLocalePtBR from '@shared/locales/pt-BR'\nimport appLocaleRo from '@shared/locales/ro'\nimport appLocaleRu from '@shared/locales/ru'\nimport appLocaleTh from '@shared/locales/th'\nimport appLocaleTr from '@shared/locales/tr'\nimport appLocaleUk from '@shared/locales/uk'\nimport appLocaleVi from '@shared/locales/vi'\nimport appLocaleZhCN from '@shared/locales/zh-CN'\nimport appLocaleZhTW from '@shared/locales/zh-TW'\n\n// Please keep the locale key in alphabetical order.\n/* eslint-disable quote-props */\nconst resources = {\n  'ar': {\n    translation: {\n      ...appLocaleAr\n    }\n  },\n  'bg': {\n    translation: {\n      ...appLocaleBg\n    }\n  },\n  'ca': {\n    translation: {\n      ...appLocaleCa\n    }\n  },\n  'de': {\n    translation: {\n      ...appLocaleDe\n    }\n  },\n  'el': {\n    translation: {\n      ...appLocaleEl\n    }\n  },\n  'en-US': {\n    translation: {\n      ...appLocaleEnUS\n    }\n  },\n  'fa': {\n    translation: {\n      ...appLocaleFa\n    }\n  },\n  'fr': {\n    translation: {\n      ...appLocaleFr\n    }\n  },\n  'hu': {\n    translation: {\n      ...appLocaleHu\n    }\n  },\n  'id': {\n    translation: {\n      ...appLocaleId\n    }\n  },\n  'it': {\n    translation: {\n      ...appLocaleIt\n    }\n  },\n  'ja': {\n    translation: {\n      ...appLocaleJa\n    }\n  },\n  'nl': {\n    translation: {\n      ...appLocaleNl\n    }\n  },\n  'ko': {\n    translation: {\n      ...appLocaleKo\n    }\n  },\n  'pl': {\n    translation: {\n      ...appLocalePl\n    }\n  },\n  'pt-BR': {\n    translation: {\n      ...appLocalePtBR\n    }\n  },\n  'ro': {\n    translation: {\n      ...appLocaleRo\n    }\n  },\n  'ru': {\n    translation: {\n      ...appLocaleRu\n    }\n  },\n  'th': {\n    translation: {\n      ...appLocaleTh\n    }\n  },\n  'tr': {\n    translation: {\n      ...appLocaleTr\n    }\n  },\n  'uk': {\n    translation: {\n      ...appLocaleUk\n    }\n  },\n  'vi': {\n    translation: {\n      ...appLocaleVi\n    }\n  },\n  'zh-CN': {\n    translation: {\n      ...appLocaleZhCN\n    }\n  },\n  'zh-TW': {\n    translation: {\n      ...appLocaleZhTW\n    }\n  }\n}\n/* eslint-enable quote-props */\n\nexport default resources\n"
  },
  {
    "path": "src/shared/locales/ar/about.js",
    "content": "export default {\n  'engine-version': 'إصدار المحرك',\n  'license': 'الرخصة',\n  'about': 'حول',\n  'release': 'الإصدار',\n  'support': 'الدعم'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/app.js",
    "content": "export default {\n  'task-list': 'قائمة التحميلات',\n  'add-task': 'إضافة تحميل',\n  'about': 'حول موتركس',\n  'preferences': 'التفضيلات...',\n  'check-for-updates': 'التحقق من وجود تحديثات ...',\n  'check-updates-now': 'تحقق الآن',\n  'checking-for-updates': 'جاري التحقق من وجود تحديثات...',\n  'check-for-updates-title': 'التحقق من وجود تحديثات',\n  'update-available-message': 'يتوفر إصدار أحدث من موتركس، تحديث الآن؟',\n  'update-not-available-message': 'لديك أحدث إصدار!',\n  'update-downloaded-message': 'جاهز للتثبيت...',\n  'update-error-message': 'حدث خطأ أثناء التحديث',\n  'engine-damaged-message': 'المحرك متضرر، الرجاء إعادة التثبيت : (',\n  'engine-missing-message': 'المحرك مفقود، الرجاء إعادة التثبيت : (',\n  'system-error-title': 'خطأ في النظام',\n  'system-error-message': 'فشل بدء تشغيل التطبيق: {{message}}',\n  'hide': 'إخفاء موتركس',\n  'hide-others': 'إخفاء الآخرين',\n  'unhide': 'إظهار الكل',\n  'show': 'إظهار موتركس',\n  'quit': 'الخروج من موتركس',\n  'under-development-message': 'عذراً، هذه الميزة قيد التطوير...',\n  'yes': 'نعم',\n  'no': 'لا',\n  'save': 'يحفظ',\n  'reset': 'ينبذ',\n  'cancel': 'إلغاء',\n  'submit': 'إرسال',\n  'gt1d': 'أكثر من يوم',\n  'hour': 'س',\n  'minute': 'د',\n  'second': 'ث'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/edit.js",
    "content": "export default {\n  'undo': 'تراجع',\n  'redo': 'إعادة',\n  'cut': 'قص',\n  'copy': 'نسخ',\n  'paste': 'لصق',\n  'delete': 'حذف',\n  'select-all': 'تحديد الكل'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/help.js",
    "content": "export default {\n  'official-website': 'موقع موتركس',\n  'manual': 'دليل الاستخدام',\n  'release-notes': 'ملاحظات الإصدار...',\n  'report-problem': 'الإبلاغ عن مشكلة',\n  'toggle-dev-tools': 'تفعيل أدوات المطور'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ar/menu.js",
    "content": "export default {\n  'app': 'موتركس',\n  'file': 'الملف',\n  'task': 'التحميل',\n  'edit': 'تعديل',\n  'window': 'النافذة',\n  'help': 'المساعدة'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/preferences.js",
    "content": "export default {\n  'basic': 'أساسية',\n  'advanced': 'متقدمة',\n  'lab': 'المختبر',\n  'save': 'حفظ وتطبيق',\n  'save-success-message': 'تم حفظ التفضيلات بنجاح',\n  'save-fail-message': 'فشل في حفظ التفضيلات',\n  'discard': 'تجاهل',\n  'startup': 'التشغيل',\n  'open-at-login': 'الفتح عند تسجيل الدخول',\n  'keep-window-state': 'المحافظة على حجم وموضع النافذة عند الخروج',\n  'auto-resume-all': 'استئناف جميع التحميلات غير المكتملة تلقائيًا',\n  'default-dir': 'المسار الافتراضي',\n  'mas-default-dir-tips': '~/Downloads نظراً لقيود آلية تحديد الصلاحيات في متجر أبل، يستحسن ضبط مجلد التحميل الافتراضي إلى',\n  'transfer-settings': 'النقل',\n  'transfer-speed-upload': 'حد سرعة الرفع',\n  'transfer-speed-download': 'حد سرعة التحميل',\n  'transfer-speed-unlimited': 'غير محدود',\n  'bt-settings': 'بت تورنت',\n  'bt-save-metadata': 'احفظ البيانات الوصفية للرابط المغناطيسي كملف تورنت',\n  'bt-auto-download-content': 'قم تلقائيا بتنزيل محتوى المغناطيس والسيل',\n  'bt-force-encryption': 'تشفير BT الإجباري',\n  'keep-seeding': 'الحفاظ على البزرة حتى يتم ايقافها يدويًا',\n  'seed-ratio': 'نسبة البذرة',\n  'seed-time': 'وقت البذرة',\n  'seed-time-unit': 'دقائق',\n  'task-manage': 'إدارة التحميلات',\n  'max-concurrent-downloads': 'الحد الأقصى من التحميلات النشطة',\n  'max-connection-per-server': 'الحد الأقصى من الاتصالات لكل خادم',\n  'new-task-show-downloading': 'إظهار التحميل تلقائيًا بعد إضافة التحميل',\n  'no-confirm-before-delete-task': 'لاتطلب التأكيد قبل حذف التحميل',\n  'continue': 'الإستمرارية',\n  'task-completed-notify': 'إشعار بعد اكتمال التحميل',\n  'auto-purge-record': 'مسح سجلات التحميل تلقائيًا عند الخروج من التطبيق',\n  'ui': 'الواجهة',\n  'appearance': 'المظهر',\n  'theme-auto': 'تلقائي',\n  'theme-light': 'فاتح',\n  'theme-dark': 'داكن',\n  'auto-hide-window': 'إخفاء النافذة تلقائيًا',\n  'run-mode': 'تشغيل كـ',\n  'run-mode-standard': 'التطبيق القياسي',\n  'run-mode-tray': 'تطبيق العلبة',\n  'run-mode-hide-tray': 'إخفاء تطبيق العلبة',\n  'tray-speedometer': 'يعرض درج شريط القوائم السرعة في الوقت الفعلي',\n  'show-progress-bar': 'عرض شريط تقدم التنزيل',\n  'language': 'اللغة',\n  'change-language': 'تغيير اللغة',\n  'hide-app-menu': 'إخفاء قائمة التطبيقات (Windows و Linux فقط)',\n  'proxy': 'الخادم الوسيط',\n  'enable-proxy': 'تفعيل الخادم الوسيط',\n  'proxy-bypass-input-tips': 'تخطي إعدادات الخادم الوسيط لهذه المضيفات والمجالات، واحدة لكل سطر',\n  'proxy-scope-download': 'تنزيل',\n  'proxy-scope-update-app': 'تحديث التطبيق',\n  'proxy-scope-update-trackers': 'تحديث المتتبعات',\n  'proxy-tips': 'عرض دليل الخادم الوسيط',\n  'bt-tracker': 'خوادم التعقب',\n  'bt-tracker-input-tips': 'خوادم التعقب، واحدة لكل سطر',\n  'bt-tracker-tips': 'مستحسن: ',\n  'sync-tracker-tips': 'مزامنة',\n  'auto-sync-tracker': 'تحديث قائمة التعقب تلقائيًا كل يوم',\n  'port': 'منافذ الاستماع',\n  'bt-port': 'منفذ BT للاستماع',\n  'dht-port': 'منفذ DHT للاستماع',\n  'security': 'الحماية',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'منفذ استماع RPC',\n  'rpc-secret': 'رمز RPC السري',\n  'rpc-secret-tips': 'عرض دليل رمز RPC السري',\n  'developer': 'المطور',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'وكيل مستخدم وهمي',\n  'aria2-conf-path': 'مسار aria2.conf المدمج',\n  'app-log-path': 'مسار سجلات التطبيق',\n  'download-session-path': 'مسار التحميلات',\n  'session-reset': 'إعادة التحميل',\n  'session-reset-confirm': 'هل أنت متأكد أنك تريد إعادة تحميله؟',\n  'factory-reset': 'العودة إلى الإعدادات الافتراضية',\n  'factory-reset-confirm': 'هل أنت متأكد من العودة إلى الإعدادات الافتراضية؟',\n  'lab-warning': '⚠️ قد يؤدي تمكين إضافات المختبر إلى تعطل التطبيق أو فقدان البيانات، لذا قرر على مسؤوليتك الخاصة!',\n  'download-protocol': 'البروتوكولات',\n  'protocols-default-client': 'تعيين كعميل افتراضي للبروتوكولات التالية',\n  'protocols-magnet': 'المغناطيس [ magnet:// ]',\n  'protocols-thunder': 'الرعد [ thunder:// ]',\n  'browser-extensions': 'الإضافات',\n  'baidu-exporter': 'مُصدر بايدو (Baidu)',\n  'browser-extensions-tips': 'مقدمة من المجتمع، ',\n  'baidu-exporter-help': 'اضغط هنا لبدء الاستخدام',\n  'auto-update': 'التحديث التلقائي',\n  'auto-check-update': 'تحقق تلقائيًا من التحديث',\n  'last-check-update-time': 'آخر مرة تم التحقق من وجود تحديثات',\n  'not-saved': 'التفضيلات غير محفوظة',\n  'not-saved-confirm': 'ستفقد التفضيلات التي تم تغييرها ، هل أنت متأكد من المغادرة؟'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/subnav.js",
    "content": "export default {\n  'task-list': 'قائمة التحميلات',\n  'preferences': 'التفضيلات'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/task.js",
    "content": "export default {\n  'active': 'جاري التحميل',\n  'waiting': 'جاري الانتظار',\n  'stopped': 'المتوقفة',\n  'new-task': 'اضافة تحميل جديد',\n  'new-bt-task': 'اضافة تحميل تورنت جديد',\n  'open-file': 'فتح ملف تورنت...',\n  'uri-task': 'رابط التحميل',\n  'torrent-task': 'تورنت',\n  'uri-task-tips': 'عنوان تحميل واحد لكل سطر، (يدعم الروابط المغناطيسية)',\n  'thunder-link-tips': 'نصيحة: قد لا تكون روابط الرعد قابلة للتحميل بعد فك التشفير',\n  'new-task-uris-required': 'الرجاء إدخال رابط تحميل صالح واحد على الأقل',\n  'new-task-torrent-required': 'الرجاء اختيار ملف تورنت',\n  'file-name': 'اسم الملف',\n  'file-extension': 'نوع الملف',\n  'file-size': 'حجم الملف',\n  'file-completed-size': 'تم التنزيل',\n  'selected-files-sum': 'الملف المختار: {{selectedFilesCount}} ملف, الحجم الكلي {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'الرجاء تحديد ملف واحد على الأقل',\n  'task-gid': 'GID',\n  'task-name': 'اسم التحميل',\n  'task-out': 'إعادة تسمية',\n  'task-out-tips': 'اختياري',\n  'task-split': 'تقسيم',\n  'task-dir': 'حفظ إلى',\n  'pause-task': 'إيقاف مؤقت',\n  'task-ua': 'UA',\n  'task-user-agent': 'وكيل المستخدم',\n  'task-authorization': 'تفويض',\n  'task-referer': 'المرجع',\n  'task-cookie': 'الكوكيز',\n  'task-proxy': 'الخادم الوسيط',\n  'task-error-info': 'خطأ',\n  'task-piece': 'قطعة',\n  'task-piece-length': 'حجم القطعة',\n  'task-num-pieces': 'قطع',\n  'task-bittorrent-info': 'معلومات التورنت',\n  'task-info-hash': 'تجزئة',\n  'task-bittorrent-creation-date': 'تاريخ الإنشاء',\n  'task-bittorrent-comment': 'تعليق',\n  'task-progress-info': 'تقدم',\n  'task-status': 'حالة',\n  'task-num-seeders': 'بزار',\n  'task-connections': 'روابط',\n  'task-file-size': 'بحجم',\n  'task-download-speed': 'سرعة التنزيل',\n  'task-upload-speed': 'سرعة التحميل',\n  'task-download-length': 'تم التنزيل',\n  'task-upload-length': 'تم الرفع',\n  'task-ratio': 'نسبة',\n  'task-peer-host': 'مضيف',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'عميل',\n  'navigate-to-downloading': 'الانتقال إلى التحميل',\n  'show-advanced-options': 'الخيارات المتقدمة',\n  'copyright-warning': 'تحذير حقوق الطبع والنشر',\n  'copyright-warning-message': 'قد يكون الملف الذي تريد تحميله محميًا بحقوق الطبع والنشر بالصوت أو بالفيديو، يرجى التأكد من حصولك على إذن للوصول إليه.',\n  'copyright-yes': 'نعم، أملك الإذن',\n  'copyright-no': 'لا، لا أملك الإذن',\n  'copyright-error-message': 'فشل في إضافة التحميل بسبب حقوق الطبع والنشر',\n  'pause-task-success': 'تم بنجاح ايقاف التحميل \"{{taskName}}\"',\n  'pause-task-fail': ' فشل في ايقاف التحميل \"{{taskName}}\"',\n  'resume-task': 'إستئناف التحميل',\n  'resume-task-success': 'تم بنجاح إستئناف التحميل \"{{taskName}}\"',\n  'resume-task-fail': 'فشل في إستئناف التحميل \"{{taskName}}\"',\n  'delete-task': 'حذف التحميل',\n  'delete-selected-tasks': 'حذف التحميلات المحددة',\n  'delete-task-confirm': 'هل أنت متأكد أنك تريد حذف تحميل \"{{taskName}}\" ؟',\n  'batch-delete-task-confirm': 'هل انت متأكد أنك تريد حذف {{count}} التحميلات دفعة واحدة',\n  'delete-task-label': 'حذف مع الملفات',\n  'delete-task-success': 'تم بنجاح حذف التحميل \"{{taskName}}\"',\n  'delete-task-fail': 'فشل في حذف التحميل \"{{taskName}}\"',\n  'remove-task-file-fail': 'فشل في حذف ملف(ات) التحميل، الرجاء حذفها يدويًا',\n  'remove-task-config-file-fail': 'فشل في حذف ملف تهيئة التحميل (config file)، يرجى حذفه يدويًا',\n  'move-task-up': 'تحريك التحميل لأعلى',\n  'move-task-down': 'تحريك التحميل لأسغل',\n  'pause-all-task': 'إيقاف جميع التحميلات',\n  'pause-all-task-success': 'تم إيقاف جميع التحميلات بنجاح',\n  'pause-all-task-fail': 'فشل في إيقاف جميع التحميلات',\n  'resume-all-task': 'إستئناف جميع التحميلات',\n  'resume-all-task-success': 'تم إستئناف جميع التحميلات بنجاح',\n  'resume-all-task-fail': 'فشل في إستئناف جميع التحميلات',\n  'select-all-task': 'تحديد جميع التحميلات',\n  'clear-recent-tasks': 'حذف التحميلات الحديثة',\n  'purge-record': 'تطهير سجل التحميل',\n  'purge-record-success': 'تم تطهير سجلات المهة بنجاح',\n  'purge-record-fail': 'فشل في تطهير سجلات التحميل',\n  'batch-delete-task-success': 'تم بنجاح حذف التحميلات دفعة واحدة',\n  'batch-delete-task-fail': 'فشل في حذف التحميلات دفعة واحدة',\n  'refresh-list': 'تحديث قائمة التحميلات',\n  'no-task': 'لا توجد تحميلات حالية',\n  'copy-link': 'نسخ الرابط',\n  'copy-link-success': 'تم نسخ الرابط بنجاح',\n  'remove-record': 'حذف سجل التحميل',\n  'remove-record-confirm': 'هل أنت متأكد انك تريد حذف سجل التحميل الخاص بـ \"{{taskName}}\" ؟',\n  'remove-record-label': 'حذف مع الملفات',\n  'remove-record-success': 'تم بنجاح حذف سجل التحميل الخاص بـ \"{{taskName}}\"',\n  'remove-record-fail': 'فشل في حذف سجل التحميل الخاص بـ \"{{taskName}}\"',\n  'show-in-folder': 'عرض التحميل في المجلد',\n  'file-not-exist': 'الملف المُستَهدَف غير موجود أو تم حذفه',\n  'file-path-error': 'خطأ في مسار الملف',\n  'opening-task-message': 'جاري فتح \"{{taskName}}\" ...',\n  'get-task-name': 'جاري جلب إسم الملف...',\n  'remaining-prefix': 'متبقى',\n  'select-torrent': 'أفلت ملف التورنت هنا، أو اضغط تحديد',\n  'task-info-dialog-title': '{{title}} تفاصيل',\n  'download-start-message': 'بدأ تحميل {{taskName}}',\n  'download-pause-message': 'وقف تحميل {{taskName}} مؤقتاً',\n  'download-stop-message': 'وقف تحميل {{taskName}}',\n  'download-error-message': 'حدث خطأ أثناء تحميل {{taskName}}',\n  'download-complete-message': 'اكتمل تحميل {{taskName}}',\n  'download-complete-notify': 'اكتمل التحميل',\n  'bt-download-complete-message': 'اكتمل تحميل {{taskName}، وعملية البَذر',\n  'bt-download-complete-notify': 'اكتمل تحميل التورنت، وعملية البَذر...',\n  'bt-download-complete-tips': 'نصيحة: يمكنك إيقاف التحميل لإنهاء عملية البَذر',\n  'bt-stopping-seeding-tip': 'جاري إيقاف عملية البَذر، سيستغرق قطع الاتصال بعض الوقت، الرجاء الانتظار...',\n  'download-fail-message': 'فشل تحميل {{taskName}}',\n  'download-fail-notify': 'فشل التحميل'\n}\n"
  },
  {
    "path": "src/shared/locales/ar/window.js",
    "content": "export default {\n  'reload': 'إعادة تحميل',\n  'close': 'إغلاق',\n  'minimize': 'تصغير',\n  'zoom': 'تكبير',\n  'toggle-fullscreen': 'تفعيل وضع ملء الشاشة',\n  'front': 'إحضار الكل إلى المقدمة'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/about.js",
    "content": "export default {\n  'engine-version': 'Версия',\n  'license': 'Лиценз',\n  'about': 'Информация',\n  'release': 'Съобщение',\n  'support': 'Подкрепа'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/app.js",
    "content": "export default {\n  'task-list': 'Задачи',\n  'add-task': 'Добавяне на задача',\n  'about': 'О Motrix',\n  'preferences': 'Меню...',\n  'check-for-updates': 'Проверка на актуализацията...',\n  'check-updates-now': 'Проверете сега',\n  'checking-for-updates': 'Проверка за актуализации...',\n  'check-for-updates-title': 'Проверка за актуализации',\n  'update-available-message': 'Новата версия на Motrix е достъпна за изтегляне, Изтеглете сега?',\n  'update-not-available-message': 'Вече използвате най-новата версия!',\n  'update-downloaded-message': 'Готово за инсталиране...',\n  'update-error-message': 'Грешка при обновяване',\n  'engine-damaged-message': 'Програмата е повредена, моля преинсталирайте :(',\n  'engine-missing-message': 'Програмата е загубена, моля преинсталирайте :(',\n  'system-error-title': 'Грешка',\n  'system-error-message': 'Грешка при стартиране на приложението: {{message}}',\n  'hide': 'Скрия Motrix',\n  'hide-others': 'Скрийте всичко останало',\n  'unhide': 'Показване на всички',\n  'show': 'Показване Motrix',\n  'quit': 'Затваряне Motrix',\n  'under-development-message': 'За съжаление тази функция все още е в процес на разработка...',\n  'yes': 'Да',\n  'no': 'Не',\n  'save': 'Запазете',\n  'reset': 'Изхвърлете',\n  'cancel': 'Отказ',\n  'submit': 'Потвърдя',\n  'gt1d': '> 1 ден',\n  'hour': 'ч',\n  'minute': 'м',\n  'second': 'с'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/edit.js",
    "content": "export default {\n  'undo': 'Отказ',\n  'redo': 'Повторя',\n  'cut': 'Нарежа',\n  'copy': 'Копирам',\n  'paste': 'Поставя',\n  'delete': 'Премахна',\n  'select-all': 'Изберете всички'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/help.js",
    "content": "export default {\n  'official-website': 'Сайт Motrix',\n  'manual': 'Инструкция',\n  'release-notes': 'Маркировки...',\n  'report-problem': 'Докладвайте за проблем',\n  'toggle-dev-tools': 'Превключване на инструменти за разработчици'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/bg/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Файл',\n  'task': 'Задача',\n  'edit': 'Редктиране',\n  'window': 'Прозорец',\n  'help': 'Грижа'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/preferences.js",
    "content": "export default {\n  'basic':'Основа',\n  'advanced':'Разширени',\n  'lab':'Лаборатория',\n  'save': 'Съхронване и прилагане',\n  'save-success-message':'настройките са запазени успешно',\n  'save-fail-message': 'грешка при запазване на настройките',\n  'discard': 'Отказ',\n  'startup': 'стартиране',\n  'open-at-login': 'стартиране на програмата заедно със стартирането на операционната система',\n  'keep-window-state':'по време на затваряне на приложението, Запишете розмера и позицията на прозореца',\n  'auto-resume-all':'автоматично възобновяване на всички недовършени задачи',\n  'default-dir':'пея по подразбиране',\n  'mas-default-dir-tips': 'поради ограничения в App Store, препоръчително е да зададете пътя по подразбиране като ~/Downloads',\n  'transfer-settings':'скоростна кутия',\n  'transfer-speed-upload': 'лимит на откат',\n  'transfer-speed-download': 'лимит за изтегляне',\n  'transfer-speed-unlimited':'Unlimited',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Запазете магнитната връзка като торент файл',\n  'bt-auto-download-content': 'Автоматично изтегляне на магнит и торент съдържание',\n  'bt-force-encryption': 'BT задължително криптиране',\n  'keep-seeding': 'Продължавайте да засявате, докато не го спрете ръчно',\n  'seed-ratio': 'Съотношение на семената',\n  'seed-time': 'Време на семената',\n  'seed-time-unit': 'минути',\n  'task-manage':'мениджър на задачи',\n  'max-concurrent-downloads': 'Максимум активни задачи',\n  'max-connection-per-server': 'максимални връзки към сървъра',\n  'new-task-show-downloading': 'автоматично показване на задача след добавяне',\n  'no-confirm-before-delete-task': 'Не се изисква потвърждение преди изтриване на задача',\n  'continue':'Продължи',\n  'task-completed-notify': 'съобщение след изтеглянето',\n  'auto-purge-record': 'автоматично почистване на записите за изтегляне след затваряне на приложението',\n  'ui': 'UI',\n  'appearance': 'външен вид',\n  'theme-auto':'автоматично',\n  'theme-light':'Светло',\n  'theme-dark':'dark',\n  'auto-hide-window': 'Автоматично отваряне на прозорци',\n  'run-mode': 'Бягай като',\n  'run-mode-standard': 'стандартно приложение',\n  'run-mode-tray': 'Приложение в таванчето',\n  'run-mode-hide-tray': 'Скриване на приложение в таванчето',\n  'tray-speedometer': 'Таблата на лентата с менюта показва скорост в реално време',\n  'language':'Език',\n  'change-language': 'промяна на езика',\n  'hide-app-menu': 'Скриване на менюто на приложението (само за Windows и Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'използване на Proxy',\n  'proxy-bypass-input-tips': 'заобикаляне на настройките на прокси за тези хостове и домейни, един по един на ред',\n  'proxy-scope-download': 'Изтегляне',\n  'proxy-scope-update-app': 'Актуализиране на приложението',\n  'proxy-scope-update-trackers': 'Актуализиране на трекери',\n  'proxy-tips': 'преглед на ръководството за прокси',\n  'bt-tracker':'Tracker сървър',\n  'bt-tracker-input-tips': 'Tracker сървър, един на ред',\n  'bt-tracker-tips': 'препоръчително:',\n  'sync-tracker-tips':'Синхронизация',\n  'auto-sync-tracker':'актуализиране на списъка с тракери всеки ден автоматично',\n  'port':'портове за слушане',\n  'bt-port':'пристанище на слушане BT',\n  'dht-port':'DHT слушане Порт',\n  'security':'сигурност',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC слушащ порт',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Гледайте инструкцията RPC Secret',\n  'developer':'developer',\n  'Mock-user-agent':'оформление User-Agent',\n  'aria2-conf-path': 'Вграден път за aria2.conf',\n  'app-log-path': 'път към дневника на приложението',\n  'download-session-path': 'качване на пътя на сесията',\n  'factory-reset': 'Настройки по подразбиране',\n  'factory-reset-confirm': 'Сигурни ли сте, че искате да се върнете към настройките по подразбиране?',\n  'lab-warning': '️ ️ включването на функциите на лабораторията може да доведе до срив на приложението и загуба на данни, решете на свой риск!',\n  'download-protocol':'протоколи',\n  'protocols-default-client':'Задаване като клиент по подразбиране за следните протоколи',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Розширения',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'предоставени от общността,',\n  'baidu-exporter-help': 'Кликнете тук, за да използвате',\n  'auto-update':'автоматично обновяване',\n  'auto-check-update':'автоматична проверка на актуализациите',\n  'last-check-update-time': 'последната актуализация е проверена',\n  'not-saved': 'Предпочитанията не са запазени',\n  'not-saved-confirm': 'Променените предпочитания ще бъдат загубени, сигурни ли сте, че ще напуснете?'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/subnav.js",
    "content": "export default {\n  'task-list':'Задачи',\n  'preferences':'Настройки'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/task.js",
    "content": "export default {\n  'active':'Активен',\n  'waiting':'чакане',\n  'stopped':'спряно',\n  'new-task': 'нова задача',\n  'new-bt-task': 'Нова BT задача',\n  'open-file': 'отваряне на торент файл...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'една URL задача в низ (поддръжка на magnet)',\n  'thunder-link-tips': 'съвет: връзките от типа Thunder може да не се зареждат след декодиране',\n  'new-task-uris-required': 'въведете валиден URL адрес на ресурс',\n  'new-task-torrent-required': 'моля, изберете торент файл',\n  'file-name': 'Име на файл',\n  'file-extension':'тип файл',\n  'file-size': 'Размер',\n  'file-completed-size': 'Изтеглено',\n  'selected-files-sum': 'избрано: {{selectedFilesCount}} файлове, общ размер {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Моля, изберете поне един файл',\n  'task-gid': 'GID',\n  'task-name':'Име на изтегляне',\n  'task-out': 'Преименуване',\n  'task-out-tips':'незадължителен',\n  'task-split': 'разделяне',\n  'task-dir': 'Запазване в',\n  'pause-task': 'пауза на задачата',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Упълномощаване',\n  'task-referer': 'препращане',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Грешка',\n  'task-piece': 'Парче',\n  'task-piece-length': 'Размер на парче',\n  'task-num-pieces': 'Парчета',\n  'task-bittorrent-info': 'Информация за торента',\n  'task-info-hash': 'Хеш',\n  'task-bittorrent-creation-date': 'Дата на създаване',\n  'task-bittorrent-comment': 'Коментирайте',\n  'task-progress-info': 'Напредък',\n  'task-status': 'Състояние',\n  'task-num-seeders': 'Сеялки',\n  'task-connections': 'Връзки',\n  'task-file-size': 'Размер',\n  'task-download-speed': 'Скорост на сваляне',\n  'task-upload-speed': 'Скорост на качване',\n  'task-download-length': 'Изтеглено',\n  'task-upload-length': 'Качено',\n  'task-ratio': 'Съотношение',\n  'task-peer-host': 'Водещ',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Клиент',\n  'navigate-to-downloading': 'напред към изтегляне',\n  'show-advanced-options': 'Разширени опции',\n  'copyright-warning':'предупреждение за авторски права',\n  'copyright-warning-message': 'файлът, който се опитвате да изтеглите, има авторски права върху видео или аудио съдържание, моля, проверете дали имате права да изтеглите този файл.',\n  'copyright-yes': 'Да, имам права',\n  'copyright-no': 'Не, нямам права',\n  'copyright-error-message': 'грешка при добавяне на задача поради проблеми с авторските права',\n  'pause-task-success': 'успешно спряна задача\" {{TaskName}}\"',\n  'pause-task-fail': 'грешка при спиране на задачата\" {{taskName}}\"',\n  'resume-task': 'възобновяване на задачата',\n  'resume-task-success': 'успешно възобновена задача\" {{TaskName}}\"',\n  'resume-task-fail': 'грешка при възобновяване на задачата\" {{taskName}}\"',\n  'delete-task': 'изтриване на задача',\n  'delete-selected-tasks': 'изтриване на избраните задачи',\n  'delete-task-confirm': 'сигурни ли Сте, че искате да изтриете задачата \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Сигурни ли сте, че искате да изтриете {{count}} задачи за зареждане в партиден режим?',\n  'delete-task-label': 'изтриване заедно с файловете',\n  'delete-task-success': 'успешно изтрита задача\" {{TaskName}}\"',\n  'delete-task-fail': 'грешка при изтриване на задача\" {{taskName}}\"',\n  'remove-task-file-fail': 'грешка при изтриване на файл (файлове) на задача, моля, изтрийте го (тях) сами',\n  'remove-task-config-file-fail': 'грешка при изтриване на конфигурационния файл на заданието, моля, изтрийте го сами',\n  'move-task-up': 'Преместване на задача нагоре',\n  'move-task-down': 'Преместване на задача надолу',\n  'pause-all-task': 'пауза на всички задачи',\n  'pause-all-task-success': 'всички задачи са успешно прекратени',\n  'pause-all-task-fail': 'грешка при спиране на всички задачи',\n  'resume-all-task': 'възобновяване на всички задачи',\n  'resume-all-task-success': 'успешно възобновени всички задачи',\n  'resume-all-task-fail': 'грешка при възобновяване на всички задачи',\n  'select-all-task': 'Изберете всички задачи',\n  'clear-recent-tasks': 'Изчистване на последните задачи',\n  'purge-record': 'Изчистване на записките за задачи',\n  'purge-record-success': 'успешно изчистени записи на задачи',\n  'purge-record-fail': 'грешка при изчистване на записите за задачи',\n  'batch-delete-task-success': 'успешно изтриване на задачи в партиден режим',\n  'batch-delete-task-fail': 'Неуспешно изтриване на задачи в партиден режим',\n  'refresh-list': 'обновяване на списъка със задачи',\n  'no-task': 'няма текущи задачи',\n  'copy-link': 'копиране на връзка',\n  'copy-link-success': 'успешно копирана връзка',\n  'remove-record': 'изтриване на запис за задача',\n  'remove-record-confirm': 'Сигурни ли сте, че искате да изтриете записа за задачата \"{{taskName}}\"?',\n  'remove-record-label':'изтриване с файлове',\n  'remove-record-success': 'успешно изтрит запис за задача\" {{taskName}}\"',\n  'remove-record-fail': 'грешка при изтриване на запис за задача \"{{taskName}}\"',\n  'show-in-folder': 'показване на файловете със задачи в папка',\n  'file-not-exist': 'търсеният файл не съществува или е изтрит',\n  'file-path-error': 'Грешка в пътя към файла',\n  'opening-task-message': 'отваряне \"{{TaskName}}\" ...',\n  'get-task-name': 'получаване на име на задача...',\n  'remaining-prefix':'ляво',\n  'select-torrent':'плъзнете торент файла тук, или натиснете Избери',\n  'task-info-dialog-title':'{{Title}} детайли',\n  'download-start-message': 'изтеглянето започна {{taskName}}',\n  'download-pause-message': 'спиране на изтеглянето {{taskName}}',\n  'download-stop-message': 'спиране на изтеглянето {{taskName}}',\n  'download-error-message': 'грешка при изтегляне {{taskName}}',\n  'download-complete-message': 'Завършено изтегляне {{taskName}}',\n  'download-complete-notify':'изтеглянето Завършено',\n  'BT-download-complete-message': 'завършено изтегляне {{TaskName}}, раздаване',\n  'BT-download-complete-notify': 'BT изтеглянето приключи, раздаване...',\n  'BT-download-complete-tips': 'съвет: можете да спрете задачата, за да спрете раздаването',\n  'bt-stopping-seeding-tip': 'Спирането на засяването ще отнеме известно време, за да прекъснете връзката, моля изчакайте...',\n  'download-fail-message': 'не може да бъде изтеглено {{taskName}}',\n  'download-fail-notify': 'грешка при зареждане'\n}\n"
  },
  {
    "path": "src/shared/locales/bg/window.js",
    "content": "export default {\n  'reload': 'Перезагрузить',\n  'close': 'Закрыть',\n  'minimize': 'Свернуть',\n  'zoom': 'Увеличение',\n  'toggle-fullscreen': 'Перейти в полноэкранный режим',\n  'front': 'Поверх всех окон'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/about.js",
    "content": "export default {\n  'engine-version': 'Versió del motor',\n  'license': 'Llicència',\n  'about': 'Sobre ',\n  'release': 'Llençaments',\n  'support': 'Suport'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/app.js",
    "content": "export default {\n  'task-list': 'Tasques',\n  'add-task': 'Afegir tasca',\n  'about': 'Sobre Motrix',\n  'preferences': 'Preferències...',\n  'check-for-updates': 'Comprovar actualitzacions...',\n  'check-updates-now': 'Comprovar ara',\n  'checking-for-updates': 'Comprovant actualitzacions...',\n  'check-for-updates-title': 'Comprovar actualitzacions',\n  'update-available-message': 'Hi ha una nova versió de Motrix. Actualitzar ara?',\n  'update-not-available-message': 'Estàs en l\\'última versió!',\n  'update-downloaded-message': 'Llest per instal·lar...',\n  'update-error-message': 'Error mentre s\\'actualitzava',\n  'engine-damaged-message': 'El motor està danyat, per favor reinstal·la :(',\n  'engine-missing-message': 'No es troba el motor, por favor reinstal·la :(',\n  'system-error-title': 'Error del sistema',\n  'system-error-message': 'L\\'aplicació va fallar en iniciar: {{message}}',\n  'hide': 'Ocultar Motrix',\n  'hide-others': 'Ocultar altres',\n  'unhide': 'Mostrar tot',\n  'show': 'Mostrar Motrix',\n  'quit': 'Sortir de Motrix',\n  'under-development-message': 'Ho sentim, aquesta característica està en desenvolupament...',\n  'yes': 'Sí',\n  'no': 'No',\n  'save': 'Desa',\n  'reset': 'Descarta',\n  'cancel': 'Cancel·lar',\n  'submit': 'Enviar',\n  'gt1d': '> 1 dia',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/edit.js",
    "content": "export default {\n  'undo': 'Desfer',\n  'redo': 'Refer',\n  'cut': 'Retallar',\n  'copy': 'Copiar',\n  'paste': 'Enganxar',\n  'delete': 'Eliminar',\n  'select-all': 'Seleccionar tot'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/help.js",
    "content": "export default {\n  'official-website': 'Lloc web de Motrix',\n  'manual': 'Manual',\n  'release-notes': 'Notes de la versió...',\n  'report-problem': 'Informar d\\'un problema',\n  'toggle-dev-tools': 'Alternar les eines de desenvolupament'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ca/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Arxiu',\n  'task': 'Tasca',\n  'edit': 'Editar',\n  'window': 'Finestra',\n  'help': 'Ajuda'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/preferences.js",
    "content": "export default {\n  'basic': 'Bàsic',\n  'advanced': 'Avançat',\n  'lab': 'Lab',\n  'save': 'Guardar i aplicar',\n  'save-success-message': 'Preferències guardades amb èxit',\n  'save-fail-message': 'Hi va haver un error al guardar les teves preferències',\n  'discard': 'Descartar',\n  'startup': 'Inici',\n  'open-at-login': 'Obrir en iniciar sessió',\n  'keep-window-state': 'Mantenir la mida i la posició de la finestra al sortir',\n  'auto-resume-all': 'Resumir automàticament totes les tasques sense finalitzar',\n  'default-dir': 'Ruta per defecte',\n  'mas-default-dir-tips': 'Degut a les restriccions de la botiga d\\'aplicacions, la ruta per defecte es recomana que sigui ~/Downloads',\n  'transfer-settings': 'Transmission',\n  'transfer-speed-upload': 'Límit de pujada',\n  'transfer-speed-download': 'Límit de baixada',\n  'transfer-speed-unlimited': 'Il·limitat',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Deseu l\\'enllaç magnet com a fitxer torrent',\n  'bt-auto-download-content': 'Descarregueu automàticament el contingut de Magnet i Torrent',\n  'bt-force-encryption': 'Forçar xifratge de BT',\n  'keep-seeding': 'Seguiu sembrant fins aturar-lo manualment',\n  'seed-ratio': 'Relació de llavors',\n  'seed-time': 'Temps de llavors',\n  'seed-time-unit': 'minuts',\n  'task-manage': 'Gestió de tasques',\n  'max-concurrent-downloads': 'Tasques màximes actives',\n  'max-connection-per-server': 'Connexions màximes per servidor',\n  'new-task-show-downloading': 'Mostrar automàticament la descàrrega després d\\'afegir una tasca',\n  'no-confirm-before-delete-task': 'No cal confirmar abans de suprimir la tasca',\n  'continue': 'Continuar',\n  'task-completed-notify': 'Notificar després que la descàrrega finalitzi',\n  'auto-purge-record': 'Purgar automàticament el registre de descàrregues en sortir',\n  'ui': 'UI',\n  'appearance': 'Aparença',\n  'theme-auto': 'Auto',\n  'theme-light': 'Clar',\n  'theme-dark': 'Fosc',\n  'auto-hide-window': 'Amaga automàticament les finestres',\n  'run-mode': 'Executa com',\n  'run-mode-standard': 'Aplicació estàndard',\n  'run-mode-tray': 'Aplicació de la safata',\n  'run-mode-hide-tray': 'Amagar l\\'aplicació de la safata',\n  'tray-speedometer': 'La safata de barres de menús mostra la velocitat en temps real',\n  'show-progress-bar': 'Mostra la barra de progrés de la descàrrega',\n  'language': 'Idioma',\n  'change-language': 'Cambiar idioma',\n  'hide-app-menu': 'Ocultar el menú (només Windows i Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Activar proxy',\n  'proxy-bypass-input-tips': 'Eviteu la configuració del servidor intermediari per a aquests amfitrions i dominis, un per línia',\n  'proxy-scope-download': 'Descàrrega',\n  'proxy-scope-update-app': 'Actualització de l\\'aplicació',\n  'proxy-scope-update-trackers': 'Actualitza els rastrejadors',\n  'proxy-tips': 'Consulteu el manual del servidor intermediari',\n  'bt-tracker': 'Seguir servidors',\n  'bt-tracker-input-tips': 'Seguir servidors, un per línia',\n  'bt-tracker-tips': 'Recomenat: ',\n  'sync-tracker-tips': 'Sincronitzar',\n  'auto-sync-tracker': 'Actualitza la llista de seguidors automàticament cada dia',\n  'port': 'Escolta Ports',\n  'bt-port': 'Port d\\'escolta BT',\n  'dht-port': 'Port d\\'escolta DHT',\n  'security': 'Seguretat',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Port d\\'Escolta RPC',\n  'rpc-secret': 'Clau RPC',\n  'rpc-secret-tips': 'Mirar manual de la clau RPC',\n  'developer': 'Desenvolupador',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Ruta incorporada per al fitxer aria2.conf',\n  'app-log-path': 'Ruta del log',\n  'download-session-path': 'Ruta de descàrrega de la sessió',\n  'factory-reset': 'Reseteig de fàbrica',\n  'factory-reset-confirm': 'Estàs segur que vols resetejar de fàbrica?',\n  'lab-warning': '⚠️ Activar les característiques \"Lab\" pot resultar en errors i pèrdua de dades!',\n  'download-protocol': 'Protocols',\n  'protocols-default-client': 'Establir com client per defecte dels següents protocols',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Extensions',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Proporcionats per la comunitat, ',\n  'baidu-exporter-help': 'Fes click aquí per veure l\\'ús',\n  'auto-update': 'Actualitzar automàticament',\n  'auto-check-update': 'Revisar actualitzacions automàticament',\n  'last-check-update-time': 'Última revisió d\\'actualitzacions',\n  'not-saved': 'Preferències no desades',\n  'not-saved-confirm': 'Les preferències modificades es perdran, esteu segur que marxareu?'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/subnav.js",
    "content": "export default {\n  'task-list': 'Tasques',\n  'preferences': 'Preferències'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/task.js",
    "content": "export default {\n  'active': 'Descarregant',\n  'waiting': 'Esperant',\n  'stopped': 'Detinguda',\n  'new-task': 'Nova Tasca',\n  'new-bt-task': 'Nova Tasca BT',\n  'open-file': 'Obrir arxiu Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Una URL de tasca per línia (suporta magnet)',\n  'thunder-link-tips': 'Tip: És possible que els enllaços Thunder no es puguin descarregar després de la descodificació.',\n  'new-task-uris-required': 'Per favor, introdueix al menys una URL de recurs vàlida',\n  'new-task-torrent-required': 'Seleccioni un arxiu torrent',\n  'file-name': 'Nom',\n  'file-extension': 'Extensió',\n  'file-size': 'Mida',\n  'file-completed-size': 'Descarregat',\n  'selected-files-sum': 'Seleccionat: {{selectedFilesCount}} arxius, mida total: {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Seleccioneu com a mínim un fitxer',\n  'task-gid': 'GID',\n  'task-name': 'Nom de la tasca',\n  'task-out': 'Canviar nom',\n  'task-out-tips': 'Opcional',\n  'task-split': 'Dividir',\n  'task-dir': 'Guardar a',\n  'pause-task': 'Pausar tasca',\n  'task-ua': 'UA',\n  'task-user-agent': 'Usuari-Agent',\n  'task-authorization': 'Autorització',\n  'task-referer': 'Referent',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Error',\n  'task-piece': 'Peça',\n  'task-piece-length': 'Mida de la peça',\n  'task-num-pieces': 'Peces',\n  'task-bittorrent-info': 'Informació del torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Data de creació',\n  'task-bittorrent-comment': 'Comentari',\n  'task-progress-info': 'Progrés, progressar',\n  'task-status': 'Estat',\n  'task-num-seeders': 'Sembradores',\n  'task-connections': 'Connexions',\n  'task-file-size': 'Mida',\n  'task-download-speed': 'Velocitat de descàrrega',\n  'task-upload-speed': 'Velocitat de pujada',\n  'task-download-length': 'Descarregat',\n  'task-upload-length': 'Carregat',\n  'task-ratio': 'Relació',\n  'task-peer-host': 'Amfitrió',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Client',\n  'navigate-to-downloading': 'Anar a Descàrregues',\n  'show-advanced-options': 'Opcions avançades',\n  'copyright-warning': 'Advertència sobre drets d\\'autor',\n  'copyright-warning-message': 'L\\'arxiu que vols descarregar pot tenir drets d\\'autor d\\'audio o vídeo, per favor asegura\\'t que tens permís per accedir a ell.',\n  'copyright-yes': 'Sí, tinc permís',\n  'copyright-no': 'No, no tinc permís.',\n  'copyright-error-message': 'No s\\'ha pogut afegir una tasca degut a un problema de drets d\\'autor',\n  'pause-task-success': 'S\\'ha pausat la tasca \"{{taskName}}\"',\n  'pause-task-fail': 'Hi ha hagut un problema al pausar la tasca \"{{taskName}}\"',\n  'resume-task': 'Resumir tasca',\n  'resume-task-success': 'S\\'ha resumido la tasca \"{{taskName}}\"',\n  'resume-task-fail': 'Hi ha hagut un error en resumir la tasca \"{{taskName}}\"',\n  'delete-task': 'Eliminar tasca',\n  'delete-selected-tasks': 'Eliminar tasques sel·leccionades',\n  'delete-task-confirm': 'Estàs segur que vols eliminar la tasca \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Esteu segur que voleu suprimir {{count}} tasques de descàrrega en un lot?',\n  'delete-task-label': 'Eliminar amb arxius',\n  'delete-task-success': 'Tasca eliminada amb èxit \"{{taskName}}\"',\n  'delete-task-fail': 'Hi ha hagut un error en eliminar la tasca \"{{taskName}}\"',\n  'remove-task-file-fail': 'No s\\'han eliminat els arxius de tasques, per favor, elimina\\'ls manualment.',\n  'remove-task-config-file-fail': 'No s\\'ha pogut eliminar l\\'arxiu de configuració de la tasca, per favor, elimina\\' manualment.',\n  'move-task-up': 'Desplaçar tasca cap a dalt',\n  'move-task-down': 'Desplaçar tasca cap a baix',\n  'pause-all-task': 'Pausar totes les tasques',\n  'pause-all-task-success': 'S\\'han pausat totes les tasques amb èxit',\n  'pause-all-task-fail': 'Hi ha hagut un error en pausar totes les tasques',\n  'resume-all-task': 'Resumir totes les tasques',\n  'resume-all-task-success': 'S\\'han resumit amb èxit totes les tasques',\n  'resume-all-task-fail': 'Hi ha hagut un error en resumir totes les tasques',\n  'select-all-task': 'Seleccioneu tota la tasca',\n  'clear-recent-tasks': 'Limpiar les darreres tasques',\n  'purge-record': 'Purgar registre de tasques',\n  'purge-record-success': 'Registres de tasques purgats amb èxit',\n  'purge-record-fail': 'No s\\'ha pogut purgar els registres de tasques',\n  'batch-delete-task-success': 'Suprimiu les tasques correctament al lot',\n  'batch-delete-task-fail': 'No s\\'ha pogut suprimir les tasques del lot',\n  'refresh-list': 'Refrescar llista de tasques',\n  'no-task': 'No hi ha tasques actuals',\n  'copy-link': 'Copiar enllaç',\n  'copy-link-success': 'Enllaç copiat amb èxit',\n  'remove-record': 'Eliminar tasca',\n  'remove-record-confirm': 'Estàs segur que vols eliminar la tasca \"{{taskName}}\"?',\n  'remove-record-label': 'Eliminar amb arxius',\n  'remove-record-success': 'S\\'ha eliminat amb èxit la tasca \"{{taskName}}\"',\n  'remove-record-fail': 'Hi ha hagut un error en eliminar la tasca \"{{taskName}}\"',\n  'show-in-folder': 'Mostrar la carpeta de la tasca',\n  'file-not-exist': 'L\\'archivo objetiu no existeix o s\\'ha eliminat',\n  'file-path-error': 'Error en la ruta de l\\'arxiu',\n  'opening-task-message': 'Obrint \"{{taskName}}\"...',\n  'get-task-name': 'Obtenint el nom de la tasca...',\n  'remaining-prefix': 'restant',\n  'select-torrent': 'Arrosega un arxiu Torrent aquí o fes click en seleccionar',\n  'task-info-dialog-title': 'Detalls de {{title}}',\n  'download-start-message': 'S\\'ha iniciat la descàrrega de {{taskName}}',\n  'download-pause-message': 'S\\'ha pausat la descàrrega de {{taskName}}',\n  'download-stop-message': 'S\\'ha detingut la descàrrega de {{taskName}}',\n  'download-error-message': 'Ha ocurrit un error en descarregar {{taskName}}',\n  'download-complete-message': 'S\\'ha terminat de descarregar {{taskName}}',\n  'download-complete-notify': 'Descàrrega completada',\n  'bt-download-complete-message': 'Descàrrega completada {{taskName}}. Compartint...',\n  'bt-download-complete-notify': 'Descàrrega BT completa. Compartint...',\n  'bt-download-complete-tips': 'Tips: Pot detenir una tasca per deixar de compartir',\n  'bt-stopping-seeding-tip': 'Aturar la sembra, es necessitarà un temps per desconnectar-se, espereu...',\n  'download-fail-message': 'No s\\'ha pogut descarregar {{taskName}}',\n  'download-fail-notify': 'Descàrrega fallida'\n}\n"
  },
  {
    "path": "src/shared/locales/ca/window.js",
    "content": "export default {\n  'reload': 'Recarregar',\n  'close': 'Tancar',\n  'minimize': 'Minimitzar',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Posar a Pantalla Completa',\n  'front': 'Posar Tot Al Front'\n}\n"
  },
  {
    "path": "src/shared/locales/de/about.js",
    "content": "export default {\n  'engine-version': 'Engine Version',\n  'license': 'Lizenz',\n  'about': 'Über',\n  'release': 'Versionen',\n  'support': 'Unterstützung anfordern'\n}\n"
  },
  {
    "path": "src/shared/locales/de/app.js",
    "content": "export default {\n  'task-list': 'Aufgaben',\n  'add-task': 'Aufgabe hinzufügen',\n  'about': 'Über Motrix',\n  'preferences': 'Einstellungen...',\n  'check-for-updates': 'Nach Updates suchen...',\n  'check-updates-now': 'Jetzt prüfen',\n  'checking-for-updates': 'Nach Updates suchen ...',\n  'check-for-updates-title': 'Nach Updates suchen',\n  'update-available-message': 'Eine neue Version von Motrix ist verfügbar, jetzt aktualisieren?',\n  'update-not-available-message': 'Sie sind auf dem neuesten Stand!',\n  'update-downloaded-message': 'Bereit zur Installation...',\n  'update-error-message': 'Aktualisierungsfehler',\n  'engine-damaged-message': 'Der Motor ist beschädigt, bitte neu installieren : (',\n  'engine-missing-message': 'Der Motor fehlt, bitte neu installieren : (',\n  'system-error-title': 'Systemfehler',\n  'system-error-message': 'Die Anwendung konnte nicht gestartet werden: {{message}}',\n  'hide': 'Motrix verbergen',\n  'hide-others': 'Andere verbergen',\n  'unhide': 'Alles anzeigen',\n  'show': 'Motrix anzeigen',\n  'quit': 'Motrix beenden',\n  'under-development-message': 'Entschuldigung, diese Funktion befindet sich in der Entwicklung...',\n  'yes': 'Ja',\n  'no': 'Nein',\n  'save': 'Speichern',\n  'reset': 'Verwerfen',\n  'cancel': 'Abbrechen',\n  'submit': 'Übernehmen',\n  'gt1d': '> 1 Tag',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/de/edit.js",
    "content": "export default {\n  'undo': 'Rückgängig',\n  'redo': 'Wiederholen',\n  'cut': 'Ausschneiden',\n  'copy': 'Kopieren',\n  'paste': 'Einfügen',\n  'delete': 'Löschen',\n  'select-all': 'Alles auswählen'\n}\n"
  },
  {
    "path": "src/shared/locales/de/help.js",
    "content": "export default {\n  'official-website': 'Motrix Website',\n  'manual': 'Handbuch',\n  'release-notes': 'Versionshinweise...',\n  'report-problem': 'Problem melden',\n  'toggle-dev-tools': 'Entwicklerwerkzeuge umschalten'\n}\n"
  },
  {
    "path": "src/shared/locales/de/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/de/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Datei',\n  'task': 'Aufgabe',\n  'edit': 'Bearbeiten',\n  'window': 'Fenster',\n  'help': 'Hilfe'\n}\n"
  },
  {
    "path": "src/shared/locales/de/preferences.js",
    "content": "export default {\n  'basic': 'Standard',\n  'advanced': 'Erweitert',\n  'lab': 'Experimentell',\n  'save': 'Speichern & übernehmen',\n  'save-success-message': 'Einstellungen erfolgreich speichern',\n  'save-fail-message': 'Speichern der Einstellungen fehlgeschlagen',\n  'discard': 'Verwerfen',\n  'startup': 'Startup',\n  'open-at-login': 'Beim Login öffnen',\n  'keep-window-state': 'Stellen Sie die Größe und Position des Fensters wieder her',\n  'auto-resume-all': 'Alle nicht abgeschlossenen Aufgaben automatisch fortsetzen',\n  'default-dir': 'Standardpfad',\n  'mas-default-dir-tips': 'Aufgrund der Einschränkungen durch Sandbox-Berechtigungen im App Store wird der Download Ordner als Standard empfohlen',\n  'transfer-settings': 'Übertragung',\n  'transfer-speed-upload': 'Upload-Limit',\n  'transfer-speed-download': 'Download-Limit',\n  'transfer-speed-unlimited': 'Unbegrenzt',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Magnetlink als Torrent-Datei speichern',\n  'bt-auto-download-content': 'Laden Sie den Magneten- und Torrent-Inhalt automatisch herunter',\n  'bt-force-encryption': 'BT Zwangskodierung',\n  'keep-seeding': 'Setzen Sie die Aussaat fort, bis Sie sie manuell stoppen',\n  'seed-ratio': 'Samenverhältnis',\n  'seed-time': 'Startzeit',\n  'seed-time-unit': 'Protokoll',\n  'task-manage': 'Aufgaben verwalten',\n  'max-concurrent-downloads': 'Maximal aktive Aufgaben',\n  'max-connection-per-server': 'Maximale Verbindungen pro Server',\n  'new-task-show-downloading': 'Nach hinzufügen einer Aufgabe zu aktiven Downloads wechseln',\n  'no-confirm-before-delete-task': 'Vor dem Löschen der Aufgabe ist keine Bestätigung erforderlich',\n  'continue': 'HTTPS/FTP Downloads fortsetzen wenn bereits angefangen',\n  'task-completed-notify': 'Benachrichtigung nach abgeschlossenen Download anzeigen',\n  'auto-purge-record': 'Download Protokoll beim Schließen der App löschen',\n  'ui': 'UI',\n  'appearance': 'Erscheinungsbild',\n  'theme-auto': 'Automatisch',\n  'theme-light': 'Hell',\n  'theme-dark': 'Dunkel',\n  'auto-hide-window': 'Fenster automatisch ausblenden',\n  'run-mode': 'Rennen wie',\n  'run-mode-standard': 'Standardanwendung',\n  'run-mode-tray': 'Infobereichsanwendung',\n  'run-mode-hide-tray': 'Infobereichsanwendung ausblenden',\n  'tray-speedometer': 'Das Menüleistenfach zeigt die Echtzeitgeschwindigkeit an',\n  'show-progress-bar': 'Fortschrittsbalken anzeigen',\n  'language': 'Sprache',\n  'change-language': 'Sprache ändern',\n  'hide-app-menu': 'App Menü ausblenden (nur auf Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Proxy aktivieren',\n  'proxy-bypass-input-tips': 'Proxy-Einstellungen für diese Hosts und Domänen umgehen, eine pro Zeile',\n  'proxy-scope-download': 'Herunterladen',\n  'proxy-scope-update-app': 'Anwendung aktualisieren',\n  'proxy-scope-update-trackers': 'Tracker aktualisieren',\n  'proxy-tips': 'Proxy-Handbuch anzeigen',\n  'bt-tracker': 'Tracker-Server',\n  'bt-tracker-input-tips': 'Tracker-Server, einer pro Zeile',\n  'bt-tracker-tips': 'Empfehlen:',\n  'sync-tracker-tips': 'Synchronisieren',\n  'auto-sync-tracker': 'Aktualisieren Sie die Trackerliste jeden Tag automatisch',\n  'port': 'Listen Ports',\n  'bt-port': 'BT Listen Port',\n  'dht-port': 'DHT Listen Port',\n  'security': 'Sicherheit',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC-Hörport',\n  'rpc-secret': 'RPC-Geheimnis',\n  'rpc-secret-tips': 'Geheime RPC-Anleitung anzeigen',\n  'developer': 'Entwickler',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'User-Agent simulieren',\n  'aria2-conf-path': 'Integrierter aria2.conf-Pfad',\n  'app-log-path': 'Appprotokollpfad',\n  'download-session-path': 'Downloadsitzungspfad',\n  'session-reset': 'Download-Session zurücksetzen',\n  'session-reset-confirm': 'Sind Sie sicher, dass Sie die Download-Session zurücksetzen wollen?',\n  'factory-reset': 'Werkseinstellungen',\n  'factory-reset-confirm': 'Sollen die Einstellungen auf die Werkseinstellungen unwiderruflich zurückgesetzt werden?',\n  'lab-warning': '⚠️ Die Aktivierung von experimentellen Funktionen kann zu App-Abstürzen oder Datenverlust führen!',\n  'download-protocol': 'Protokoll',\n  'protocols-default-client': 'Als Standardclient für die folgenden Protokolle festlegen',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Erweiterungen',\n  'baidu-exporter': 'Baidu Exporter',\n  'browser-extensions-tips': 'Von der Community bereitgestellt, ',\n  'baidu-exporter-help': 'mehr über die Verwendung zu erfahren',\n  'auto-update': 'Auto-Update',\n  'auto-check-update': 'Automatisch auf Updates prüfen',\n  'last-check-update-time': 'letzte kontrolle update - zeit',\n  'not-saved': 'Einstellungen nicht gespeichert',\n  'not-saved-confirm': 'Die geänderten Einstellungen gehen verloren. Möchten Sie wirklich gehen?'\n}\n"
  },
  {
    "path": "src/shared/locales/de/subnav.js",
    "content": "export default {\n  'task-list': 'Aufgaben',\n  'preferences': 'Einstellungen'\n}\n"
  },
  {
    "path": "src/shared/locales/de/task.js",
    "content": "export default {\n  'active': 'Aktiv',\n  'waiting': 'Warteschlange',\n  'stopped': 'Gestoppt',\n  'new-task': 'Neue Aufgabe',\n  'new-bt-task': 'Neue BT Aufgabe',\n  'open-file': 'Torrent-Datei öffnen...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Eine Download URL pro Zeile (magnet wird unterstützt)',\n  'thunder-link-tips': 'Tipp: Thunder Links werden möglicherweise nach dem dekodieren nicht heruntergeladen',\n  'new-task-uris-required': 'Bitte geben Sie mindestens eine gültige URL ein',\n  'new-task-torrent-required': 'Bitte wählen Sie eine Torrent-Datei',\n  'file-name': 'Dateiname',\n  'file-extension': 'Dateityp',\n  'file-size': 'Dateigröße',\n  'file-completed-size': 'Heruntergeladen',\n  'selected-files-sum': 'Ausgewählt: {{selectedFilesCount}} Dateien, insgesamt {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Bitte wählen Sie mindestens eine Datei aus',\n  'task-gid': 'GID',\n  'task-name': 'Aufgaben Name',\n  'task-out': 'Dateiname',\n  'task-out-tips': 'Optional',\n  'task-split': 'Splits',\n  'task-dir': 'Ordner',\n  'pause-task': 'Aufgabe pausieren',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorisierung',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Error',\n  'task-piece': 'Stück',\n  'task-piece-length': 'Stückgröße',\n  'task-num-pieces': 'Stücke',\n  'task-bittorrent-info': 'Torrent Info',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Erstellungsdatum',\n  'task-bittorrent-comment': 'Kommentar',\n  'task-progress-info': 'Fortschritt',\n  'task-status': 'Status',\n  'task-num-seeders': 'Sämaschinen',\n  'task-connections': 'Verbindungen',\n  'task-file-size': 'Größe',\n  'task-download-speed': 'Download-Geschwindigkeit',\n  'task-upload-speed': 'Upload-Geschwindigkeit',\n  'task-download-length': 'Heruntergeladen',\n  'task-upload-length': 'Hochgeladen',\n  'task-ratio': 'Verhältnis',\n  'task-peer-host': 'Gastgeber',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Klient',\n  'navigate-to-downloading': 'Navigiere zu aktive Downloads',\n  'show-advanced-options': 'Erweiterte Optionen',\n  'copyright-warning': 'Copyright Warnung',\n  'copyright-warning-message': 'Die Datei, die Sie herunterladen möchten, könnte unter Copyright stehen. Bitte überprüfen Sie ob Sie in Besitz der notwendigen Lizenz sind.',\n  'copyright-yes': 'Ja, Ich habe',\n  'copyright-no': 'Nein',\n  'copyright-error-message': 'Aufgabe konnte Aufgrund von Copyright Problemen nicht hinzugefügt werden',\n  'pause-task-success': 'Aufgabe \"{{taskName}}\" erfolgreich pausiert',\n  'pause-task-fail': 'Aufgabe \"{{taskName}}\" pausieren fehlgeschlagen',\n  'resume-task': 'Aufgabe fortsetzen',\n  'resume-task-success': 'Aufgabe \"{{taskName}}\" erfolgreich fortgesetzt',\n  'resume-task-fail': 'Aufgabe \"{{taskName}}\" fortsetzen ist fehlgschlagen',\n  'delete-task': 'Aufgabe löschen',\n  'delete-selected-tasks': 'Ausgewählte Aufgaben löschen',\n  'delete-task-confirm': 'Den Download von \"{{taskName}}\" unwiederruflich löschen?',\n  'batch-delete-task-confirm': 'Möchten Sie wirklich {{count}} Download-Aufgaben im Batch entfernen?',\n  'delete-task-label': 'Datei auch löschen',\n  'delete-task-success': 'Aufgabe \"{{taskName}}\" erfolgreich gelöscht',\n  'delete-task-fail': 'Aufgabe \"{{taskName}}\" löschen fehlgeschlagen',\n  'remove-task-file-fail': 'Aufgaben Datei löschen fehlgeschlagen, bitte manuell löschen',\n  'remove-task-config-file-fail': 'Aufgaben Konfigurationsdatei löschen fehlgeschlagen, bitte manuell löschen',\n  'move-task-up': 'Aufgabe nach oben verschieben',\n  'move-task-down': 'Aufgabe nach unten verschieben',\n  'pause-all-task': 'Alle Aufgaben pausieren',\n  'pause-all-task-success': 'Alle Aufgaben erfolgreich pausiert',\n  'pause-all-task-fail': 'Die Aufgaben konnten nicht pausiert werden',\n  'resume-all-task': 'Alle Aufgaben fortsetzen',\n  'resume-all-task-success': 'Alle Aufgaben erfolgreich fortgesetzt',\n  'resume-all-task-fail': 'Fortsetzen aller Aufgaben fehlgeschlagen',\n  'select-all-task': 'Wählen Sie alle Aufgaben aus',\n  'clear-recent-tasks': 'Letzte Aufgaben entfernen',\n  'purge-record': 'Aufgaben Protokoll löschen',\n  'purge-record-success': 'Aufgabenprotokoll erfolgreich gelöscht',\n  'purge-record-fail': 'Aufgabenprotokoll konnte nicht gelöscht werden',\n  'batch-delete-task-success': 'Aufgaben im Batch erfolgreich löschen',\n  'batch-delete-task-fail': 'Fehler beim Löschen von Aufgaben im Stapel',\n  'refresh-list': 'Aufgabenliste aktualisieren',\n  'no-task': 'Keine Downloads aktiv',\n  'copy-link': 'Link kopieren',\n  'copy-link-success': 'Link erfolgreich kopiert',\n  'remove-record': 'Aufgaben Aufzeichnung löschen',\n  'remove-record-confirm': 'Soll die Aufzeichnung von \"{{taskName}}\" gelöscht werden?',\n  'remove-record-label': 'Datei auch löschen',\n  'remove-record-success': 'Aufzeichnung von \"{{taskName}}\" erfolgreich gelöscht',\n  'remove-record-fail': 'Aufzeichnung von \"{{taskName}}\" konnte nicht gelöscht werden',\n  'show-in-folder': 'Aufgabe in Ordner anzeigen',\n  'file-not-exist': 'Datei existiert nicht oder wurde gelöscht',\n  'file-path-error': 'Dateipfadfehler',\n  'opening-task-message': 'Öffne \"{{taskName}}\" ...',\n  'get-task-name': 'Aufgabenbezeichnung bekommen...',\n  'remaining-prefix': 'Verbleibend',\n  'select-torrent': 'Torrent mit Drag & Drop hinzufügen, oder klicken um auszuwählen',\n  'task-info-dialog-title': '{{title}} Details',\n  'download-start-message': 'Starte Download von {{taskName}}',\n  'download-pause-message': 'Pausiere Download von {{taskName}}',\n  'download-stop-message': 'Download von {{taskName}} gestoppt',\n  'download-error-message': 'Fehler beim Download von {{taskName}} aufgetreten',\n  'download-complete-message': 'Download von {{taskName}} abgeschlossen',\n  'download-complete-notify': 'Download abgeschlossen',\n  'bt-download-complete-message': 'Download von {{taskName}} abgeschlossen, aussaat...',\n  'bt-download-complete-notify': 'BT Download abgeschlossen, Aussaat...',\n  'bt-download-complete-tips': 'Tipps: Sie können die Aufgabe stoppen, die aussaat zu beenden',\n  'bt-stopping-seeding-tip': 'Wenn Sie die Aussaat beenden, dauert es einige Zeit, bis die Verbindung getrennt ist. Bitte warten Sie ...',\n  'download-fail-message': 'Download von {{taskName}} fehlgeschlagen',\n  'download-fail-notify': 'Download fehlgeschlagen'\n}\n"
  },
  {
    "path": "src/shared/locales/de/window.js",
    "content": "export default {\n  'reload': 'Neu laden',\n  'close': 'Schließen',\n  'minimize': 'Minimieren',\n  'zoom': 'Zoomen',\n  'toggle-fullscreen': 'Vollbildmodus aktivieren',\n  'front': 'Alles in den Vordergrund bringen'\n}\n"
  },
  {
    "path": "src/shared/locales/el/about.js",
    "content": "export default {\n  'engine-version': 'Έκδοση Μηχανής',\n  'license': 'Άδεια',\n  'about': 'Σχετικά',\n  'release': 'Έκδοση',\n  'support': 'Υποστήριξη'\n}\n"
  },
  {
    "path": "src/shared/locales/el/app.js",
    "content": "export default {\n  'task-list': 'Καθήκοντα',\n  'add-task': 'Προσθήκη Εργασίας',\n  'about': 'Σχετικά με το Motrix',\n  'preferences': 'Προτιμήσεις...',\n  'check-for-updates': 'Έλεγχος για Ενημερώσεις...',\n  'check-updates-now': 'Έλεγχος τώρα',\n  'checking-for-updates': 'Γίνεται Έλεγχος για Ενημερώσεις ...',\n  'check-for-updates-title': 'Έλεγχος για ενημερώσεις',\n  'update-available-message': 'Μία νέα έκδοση του Motrix είναι διαθέσιμη, ενημέρωση τώρα;',\n  'update-not-available-message': 'Έχετε τη τελευταία έκδοση!',\n  'update-downloaded-message': 'Έτοιμο για εγκατάσταση...',\n  'update-error-message': 'Σφάλμα κατά την Ενημέρωση',\n  'engine-damaged-message': 'Σφάλμα στη μηχανή, παρακαλώ κάντε επανεγκατάσταση : (',\n  'engine-missing-message': 'Η μηχανή λείπει, παρακαλώ κάντε επανεγκατάσταση : (',\n  'system-error-title': 'Σφάλμα Συστήματος',\n  'system-error-message': 'Η εκκίνηση της εφαρμογής απέτυχε: {{message}}',\n  'hide': 'Κρύψε το Motrix',\n  'hide-others': 'Κρύψε τα υπόλοιπα',\n  'unhide': 'Εμφάνιση όλων',\n  'show': 'Εμφάνιση του Motrix',\n  'quit': 'Κλείσιμο του Motrix',\n  'under-development-message': 'Συγγνώμη, αυτή η λειτουργία είναι υπό ανάπτυξη...',\n  'yes': 'Ναι',\n  'no': 'Όχι',\n  'save': 'Σώσει',\n  'reset': 'Απορρίπτω',\n  'cancel': 'Ακύρωση',\n  'submit': 'Υποβολή',\n  'gt1d': '> 1 μέρα',\n  'hour': 'ωρ',\n  'minute': 'λ',\n  'second': 'δ'\n}\n"
  },
  {
    "path": "src/shared/locales/el/edit.js",
    "content": "export default {\n  'undo': 'Αναίρεση',\n  'redo': 'Ακύρωση Αναίρεσης',\n  'cut': 'Αποκοπή',\n  'copy': 'Αντιγραφή',\n  'paste': 'Επικόλληση',\n  'delete': 'Διαγραφή',\n  'select-all': 'Επιλογή Όλων'\n}\n"
  },
  {
    "path": "src/shared/locales/el/help.js",
    "content": "export default {\n  'official-website': 'Ιστοσελίδα του Motrix',\n  'manual': 'Εγχειρίδιο Οδηγιών',\n  'release-notes': 'Σημειώσεις έκδοσης...',\n  'report-problem': 'Αναφέρετε ένα πρόβλημα',\n  'toggle-dev-tools': 'Εμφάνιση εργαλείων για προγραμματιστές'\n}\n"
  },
  {
    "path": "src/shared/locales/el/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/el/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Αρχείο',\n  'task': 'Εργασία',\n  'edit': 'Επεξεργασία',\n  'window': 'Παράθυρο',\n  'help': 'Βοήθεια'\n}\n"
  },
  {
    "path": "src/shared/locales/el/preferences.js",
    "content": "export default {\n  'basic': 'Βασικές',\n  'advanced': 'Προχωρημένες',\n  'lab': 'Εργαστήριο',\n  'save': 'Αποθήκευση & Εφαρμογή',\n  'save-success-message': 'Οι προτιμήσεις αποθηκεύτηκαν επιτυχώς',\n  'save-fail-message': 'Η αποθήκευση των προτιμήσεων απέτυχε',\n  'discard': 'Απόρριψη',\n  'startup': 'Εκκίνηση',\n  'open-at-login': 'Εκκίνηση κατά τη σύνδεση',\n  'keep-window-state': 'Αποθήκευσε το μέγεθος και τη θέση του παραθύρου κατά την έξοδο',\n  'auto-resume-all': 'Συνέχισε αυτόματα όλες τις μη τελειωμένες λήψεις',\n  'default-dir': 'Προεπιλεγμένος φάκελος λήψεων',\n  'mas-default-dir-tips': 'Λόγω των περιορισμών δικαιωμάτων του App Store,ο προεπιλεγμένος φάκελος λήψεων συστήνεται να είναι το ~/Downloads',\n  'transfer-settings': 'Μετάδοση',\n  'transfer-speed-upload': 'Όριο αποστολής',\n  'transfer-speed-download': 'Όριο λήψης',\n  'transfer-speed-unlimited': 'Χωρίς όριο',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Αποθηκεύστε το σύνδεσμο μαγνήτη ως αρχείο torrent',\n  'bt-auto-download-content': 'Αυτόματη λήψη περιεχομένου μαγνήτη και χείμαρξης',\n  'bt-force-encryption': 'Εξαναγκασμός κρυπτογράφησης BT',\n  'keep-seeding': 'Συνεχίστε να σπέρνετε μέχρι να το σταματήσετε χειροκίνητα',\n  'seed-ratio': 'Αναλογία σπόρου',\n  'seed-time': 'Χρόνος σπόρου',\n  'seed-time-unit': 'λεπτά',\n  'task-manage': 'Διαχείριση εργασιών',\n  'max-concurrent-downloads': 'Μέγιστες ενεργές Εργασίες',\n  'max-connection-per-server': 'Μέγιστες συνδέσεις ανά Σέρβερ',\n  'new-task-show-downloading': 'Δείξε αυτόματα τη λήψη μετά τη πρόσθεση της εργασίας',\n  'no-confirm-before-delete-task': 'Δεν χρειάζεται επιβεβαίωση πριν τη διαγραφή της εργασίας',\n  'continue': 'Συνέχεια',\n  'task-completed-notify': 'Ειδοποίηση μόλις τελειώσει η λήψη',\n  'auto-purge-record': 'Καθάρισε αυτόματα τη λίστα λήψεων κατά το κλείσιμο της εφαρμογής',\n  'ui': 'UI',\n  'appearance': 'Θέμα',\n  'theme-auto': 'Αυτόματο',\n  'theme-light': 'Ανοιχτό',\n  'theme-dark': 'Σκούρο',\n  'auto-hide-window': 'Αυτόματη απόκρυψη του παραθύρου',\n  'run-mode': 'Εκτέλεση ως',\n  'run-mode-standard': 'Κανονική εφαρμογή',\n  'run-mode-tray': 'Εφαρμογή περιοχής ειδοποιήσεων',\n  'run-mode-hide-tray': 'Απόκρυψη εφαρμογής της περιοχής ειδοποιήσεων',\n  'tray-speedometer': 'Δείξε τη ταχύτητα στο μενου',\n  'show-progress-bar': 'Εμφάνιση μπάρας προόδου λήψης',\n  'language': 'Γλώσσα',\n  'change-language': 'Αλλαγή Γλώσσας',\n  'hide-app-menu': 'Κρύψε το μενού της εφαρμογής (μόνο για Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Χρησιμοίησε  Proxy',\n  'proxy-bypass-input-tips': 'Μη χρησιμοποιήσεις  ρυθμίσεις proxy για αυτούς τους Hosts και τα Domains, ένα ανά γραμμή',\n  'proxy-scope-download': 'Λήψη',\n  'proxy-scope-update-app': 'Ενημέρωση εφαρμογής',\n  'proxy-scope-update-trackers': 'Ενημέρωση Trackers',\n  'proxy-tips': 'Εμφάνιση εγχειριδίου Proxy',\n  'bt-tracker': 'Tracker Servers',\n  'bt-tracker-input-tips': 'Tracker servers, ένα ανά γραμμή',\n  'bt-tracker-tips': 'Συνιστόνται: ',\n  'sync-tracker-tips': 'Συγχρονισμός',\n  'auto-sync-tracker': 'Ανανέωση της λίστας των tracker κάθε μέρα αυτόματα',\n  'port': 'Ενεργές θύρες',\n  'bt-port': 'Ενεργή θύρα BT',\n  'dht-port': 'Ενεργή θύρα DHT',\n  'security': 'Ασφάλεια',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Θύρα Ακρόασης RPC',\n  'rpc-secret': 'Μυστικό για το RPC',\n  'rpc-secret-tips': 'Εμφάνιση εγχειριδίου για το Μυστικό RPC',\n  'developer': 'Προγραμματιστής',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Πλαστό User-Agent',\n  'aria2-conf-path': 'Ενσωματωμένη διαδρομή aria2.conf',\n  'app-log-path': 'Διαδρομή για το αρχείο log της εφαρμογής',\n  'download-session-path': 'Διαδρομή λήψεων για αυτή τη συνεδρία',\n  'factory-reset': 'Επαναφορά στις εργοστασιακές ρυθμίσεις',\n  'factory-reset-confirm': 'Είστε σίγουροι ότι θέλετε να επαναφέρετε τις εργοστασιακές ρυθμίσεις;',\n  'lab-warning': '⚠️ Η ενεργοποίηση ρυθμίσεων Εργαστηρίου μπορεί να οδηγήσει σε κολλήματα στην εφαρμογή ή απώλεια δεδομένων, αποφασίστε με δικό σας ρίσκο',\n  'download-protocol': 'Πρωτόκολλα',\n  'protocols-default-client': 'Ρύθμιση ως προεπιλεγμένο πρόγραμμα για τα παρακάτω πρωτόκολλα',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Επεκτάσεις',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Παρέχεται από την κοινότητα, ',\n  'baidu-exporter-help': 'Κάντε κλικ εδώ για χρήση',\n  'auto-update': 'Αυτόματη ενημέρωση',\n  'auto-check-update': 'Αυτόματος έλεγχος για ενημερώσεις',\n  'last-check-update-time': 'Τελευταία φορά που έγινε έλεγχος για Ενημερώσεις',\n  'not-saved': 'Οι προτιμήσεις δεν αποθηκεύτηκαν',\n  'not-saved-confirm': 'Οι αλλαγμένες προτιμήσεις θα χαθούν, είστε σίγουροι ότι θα φύγετε;'\n}\n"
  },
  {
    "path": "src/shared/locales/el/subnav.js",
    "content": "export default {\n  'task-list': 'Εργασίες',\n  'preferences': 'Προτιμήσεις'\n}\n"
  },
  {
    "path": "src/shared/locales/el/task.js",
    "content": "export default {\n  'active': 'Γίνεται λήψη',\n  'waiting': 'Σε αναμονή',\n  'stopped': 'Σταματημένα',\n  'new-task': 'Νέα εργασία',\n  'new-bt-task': 'Νέα εργασία BT',\n  'open-file': 'Άνοιγμα αρχείου Torrent ...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Ένα url εργασίας ανά γραμμή (υποστηρίζονται magnet)',\n  'thunder-link-tips': 'Συμβουλή: Οι σύνδεσμοι Thunder μπορεί να μην είναι δυνατόν να κατέβουν μετά την αποκωδικοποίηση',\n  'new-task-uris-required': 'Παρακαλώ εισάγετε τουλάχιστον ένα έγκυρο url',\n  'new-task-torrent-required': 'Παρακαλώ επιλέξτε ένα αρχεί torrent',\n  'file-name': 'Όνομα αρχείου',\n  'file-extension': 'Τύπος αρχείου',\n  'file-size': 'Μέγεθος αρχείου',\n  'file-completed-size': 'Έγινε λήψη',\n  'selected-files-sum': 'Επιλεγμένα: {{selectedFilesCount}} αρχεία, συνολικό μέγεθος {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Επιλέξτε τουλάχιστον ένα αρχείο',\n  'task-gid': 'GID',\n  'task-name': 'Όνομα εργασίας',\n  'task-out': 'Άλλο Όνομα',\n  'task-out-tips': 'Προαιρετικό',\n  'task-split': 'Κομμάτια',\n  'task-dir': 'Αποθήκευση σε',\n  'pause-task': 'Παύση εργασίας',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Εξουσιοδότηση',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Λάθος',\n  'task-piece': 'Κομμάτι',\n  'task-piece-length': 'Μέγεθος κομματιού',\n  'task-num-pieces': 'Κομμάτια',\n  'task-bittorrent-info': 'Πληροφορίες Torrent',\n  'task-info-hash': 'Χασίσι',\n  'task-bittorrent-creation-date': 'Ημερομηνία δημιουργίας',\n  'task-bittorrent-comment': 'Σχόλιο',\n  'task-progress-info': 'Πρόοδος',\n  'task-status': 'Κατάσταση',\n  'task-num-seeders': 'Σπόροι',\n  'task-connections': 'Συνδέσεις',\n  'task-file-size': 'Μέγεθος',\n  'task-download-speed': 'Ταχύτητα μεταφόρτωσης',\n  'task-upload-speed': 'Ταχύτητα μεταφόρτωσης',\n  'task-download-length': 'Έγινε λήψη',\n  'task-upload-length': 'Μεταφορτώθηκε',\n  'task-ratio': 'Αναλογία',\n  'task-peer-host': 'Πλήθος',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Πελάτης',\n  'navigate-to-downloading': 'Πήγαινε στις λήψεις',\n  'show-advanced-options': 'Ρυθμίσεις για προχωρημένους',\n  'copyright-warning': 'Προειδοποίηση για Πνευματικά Δικαιώματα',\n  'copyright-warning-message': 'Το αρχείο που θέλετε να κατεβάσετε μπορεί να περιέχει οπτικό ή ακουστικό υλικό που προστατεύεται από πνευματικά δικαιώματα, παρακαλώ σιγουρευτείτε ότι έχετε άδεια πρόσβασης σε αυτό.',\n  'copyright-yes': 'Ναι, έχω άδεια',\n  'copyright-no': 'Όχι, δεν έχω άδεια',\n  'copyright-error-message': 'Η πρόσθεση του αρχείου απέτυχε λόγω προστασίας πνευματικών δικαιωμάτων',\n  'pause-task-success': 'Η παύση της εργασίας \"{{taskName}}\" ολοκληρώθηκε επιτυχώς',\n  'pause-task-fail': 'Η παύση της εργασίας \"{{taskName}}\" απέτυχε',\n  'resume-task': 'Συνέχεια Εργασίας',\n  'resume-task-success': 'Η εργασία \"{{taskName}}\" συνέχισε επιτυχώς',\n  'resume-task-fail': 'Η εργασία \"{{taskName}}\" απέτυχε να συνεχίσει',\n  'delete-task': 'Διαγραφή εργασίας',\n  'delete-selected-tasks': 'Διαγραφή επιλεγμένων εργασιών',\n  'delete-task-confirm': 'Είστε σίγουροι ότι θέλετε να διαγράψετε την εργασία λήψης \"{{taskName}}\";',\n  'batch-delete-task-confirm': 'Είστε σίγουροι ότι θέλετε να διαγράψετε {{count}} εργασίες λήψης μαζί;',\n  'delete-task-label': 'Διαγραφή μαζί με το αρχείο',\n  'delete-task-success': 'Επιτυχής διαγραφή της εργασίας \"{{taskName}}\"',\n  'delete-task-fail': 'Η διαγραφή της εργασίας \"{{taskName}}\" απέτυχε',\n  'remove-task-file-fail': 'Η διαγραφή του αρχείου/των αρχείων της εργασίας απέτυχε, παρακαλώ διαγράψτε τα χειροκίνητα',\n  'remove-task-config-file-fail': 'Η διαγραφή του αρχείου ρυθμίσεων της εργασίας απέτυχε, παρακαλώ διαγράψτε το χειροκίνητα',\n  'move-task-up': 'Μεταφορά της Εργασίας Πάνω',\n  'move-task-down': 'Μεταφορά της Εργασίας Κάτω',\n  'pause-all-task': 'Παύση όλων τον εργασιών',\n  'pause-all-task-success': 'Η παύση όλων των εργασιών ήταν επιτυχής',\n  'pause-all-task-fail': 'Αποτυχία στη παύση όλων των εργασιών',\n  'resume-all-task': 'Συνέχεια όλων των εργασιών',\n  'resume-all-task-success': 'Όλες οι εργασίες συνέχισαν επιτυχώς',\n  'resume-all-task-fail': 'Αποτυχία στη συνέχεια όλων τον εργασιών',\n  'select-all-task': 'Επιλογή όλων των εργασιών',\n  'clear-recent-tasks': 'Καθαρισμός πρόσφατων λήψεων',\n  'purge-record': 'Καθαρισμός αρχείου λήψεων',\n  'purge-record-success': 'Επιτυχής καθαρισμός αρχείου λήψεων',\n  'purge-record-fail': 'Αποτυχία στον καθαρισμό αρχείου λήψεων',\n  'batch-delete-task-success': 'Η μαζική διαγραφή εργασιών ήταν επιτυχής',\n  'batch-delete-task-fail': 'Απέτυχε η μαζική διαγραφή εργασιών',\n  'refresh-list': 'Επαναφόρτωση λίστας εργασιών',\n  'no-task': 'Δεν υπάρχουν εργασίες αυτή τη στιγμή',\n  'copy-link': 'Αντιγραφή συνδέσμου',\n  'copy-link-success': 'Επιτυχής αντιγραφή συνδέσμου',\n  'remove-record': 'Αφαίρεση αρχείου εργασίας',\n  'remove-record-confirm': 'Είστε σίγουροι ότι θέλετε να αφαιρέσετε το αρχείο εργασίας για το \"{{taskName}}\"?',\n  'remove-record-label': 'Διαγραφή μαζί με τα αρχεία',\n  'remove-record-success': 'Επιτυχής διαγραφή της εγγραφής της εργασίας \"{{taskName}}\"',\n  'remove-record-fail': 'Η διαγραφή της εγγραφής της εργασίας\"{{taskName}}\" απέτυχε',\n  'show-in-folder': 'Εμφάνιση της εργασίας στον φάκελο',\n  'file-not-exist': 'TΤο αρχείο δεν υπάρχει ή έχει διαγραφεί',\n  'file-path-error': 'Σφάλμα στο μονοπάτι για το αρχείο',\n  'opening-task-message': 'Άνοιγμα του \"{{taskName}}\" ...',\n  'get-task-name': 'Λήψη ονόματος εργασίας...',\n  'remaining-prefix': 'Απομένει',\n  'select-torrent': 'Σύρτε το αρχείο torrent εδώ, ή κάντε κλικ για επιλογή',\n  'task-info-dialog-title': 'Λεπτομέρειες για το {{title}}',\n  'download-start-message': 'Η λήψη του {{taskName}} ξεκίνησε',\n  'download-pause-message': 'Η λήψη του {{taskName}} σταμάτησε',\n  'download-stop-message': 'Η λήψη του {{taskName}} διακόπηκε',\n  'download-error-message': 'Σφάλμα κατά τη λήψη του {{taskName}}',\n  'download-complete-message': 'Η λήψη του {{taskName}} ολοκληρώθηκε',\n  'download-complete-notify': 'Η λήψη ολοκληρώθηκε',\n  'bt-download-complete-message': 'Η λήψη του {{taskName}} ολοκληρώθηκε, γίνεται διαμοιρασμός',\n  'bt-download-complete-notify': 'Μία λήψη BT ολοκληρώθηκε, γίνεται διαμοιρασμός...',\n  'bt-download-complete-tips': 'Συμβουλή: Μπορείτε να διακόψετε μία διεργασία για να σταματήσει ο διαμοιρασμός',\n  'bt-stopping-seeding-tip': 'Γίνεται διακοπή του διαμοιρασμού, μπορεί να πάρει λίγη ώρα για την αποσύνδεση, παρακαλώ περιμένετε',\n  'download-fail-message': 'Η λήψη του {{taskName}} απέτυχε',\n  'download-fail-notify': 'Η λήψη απέτυχε'\n}\n"
  },
  {
    "path": "src/shared/locales/el/window.js",
    "content": "export default {\n  'reload': 'Επαναφόρτωση',\n  'close': 'Κλείσιμο',\n  'minimize': 'Ελαχιστοποίηση',\n  'zoom': 'Ζουμ',\n  'toggle-fullscreen': 'Είσοδος σε πλήρη οθόνη',\n  'front': 'Εμφάνιση όλων μπροστά'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/about.js",
    "content": "export default {\n  'engine-version': 'Engine Version',\n  'license': 'License',\n  'about': 'About',\n  'release': 'Releases',\n  'support': 'Support'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/app.js",
    "content": "export default {\n  'task-list': 'Tasks',\n  'add-task': 'Add Task',\n  'about': 'About Motrix',\n  'preferences': 'Preferences...',\n  'check-for-updates': 'Check for Updates...',\n  'check-updates-now': 'Check now',\n  'checking-for-updates': 'Checking for updates ...',\n  'check-for-updates-title': 'Check for Updates',\n  'update-available-message': 'A newer version of Motrix is available, update now?',\n  'update-not-available-message': 'You are up-to-date!',\n  'update-downloaded-message': 'Ready to install...',\n  'update-error-message': 'Update Error',\n  'engine-damaged-message': 'The engine is damaged, please reinstall : (',\n  'engine-missing-message': 'The engine is missing, please reinstall : (',\n  'system-error-title': 'System Error',\n  'system-error-message': 'Application startup failed: {{message}}',\n  'hide': 'Hide Motrix',\n  'hide-others': 'Hide Others',\n  'unhide': 'Show All',\n  'show': 'Show Motrix',\n  'quit': 'Quit Motrix',\n  'under-development-message': 'Sorry, this feature is under development...',\n  'yes': 'Yes',\n  'no': 'No',\n  'save': 'Save',\n  'reset': 'Discard',\n  'cancel': 'Cancel',\n  'submit': 'Submit',\n  'gt1d': '> 1 day',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/edit.js",
    "content": "export default {\n  'undo': 'Undo',\n  'redo': 'Redo',\n  'cut': 'Cut',\n  'copy': 'Copy',\n  'paste': 'Paste',\n  'delete': 'Delete',\n  'select-all': 'Select All'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/help.js",
    "content": "export default {\n  'official-website': 'Motrix Website',\n  'manual': 'Manual',\n  'release-notes': 'Release Notes...',\n  'report-problem': 'Report Problem',\n  'toggle-dev-tools': 'Toggle Developer Tools'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'File',\n  'task': 'Task',\n  'edit': 'Edit',\n  'window': 'Window',\n  'help': 'Help'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/preferences.js",
    "content": "export default {\n  'basic': 'Basic',\n  'advanced': 'Advanced',\n  'lab': 'Lab',\n  'save': 'Save & Apply',\n  'save-success-message': 'Preferences saved successfully',\n  'save-fail-message': 'Preferences failed to save',\n  'discard': 'Discard',\n  'startup': 'Startup',\n  'open-at-login': 'Open at login',\n  'keep-window-state': 'Keep size and position of the window when exited',\n  'auto-resume-all': 'Automatically resume all unfinished tasks',\n  'default-dir': 'Default Path',\n  'mas-default-dir-tips': 'Due to sandbox permission restrictions of the App Store, the default download directory is recommended to be set to ~/Downloads',\n  'transfer-settings': 'Transmission',\n  'transfer-speed-upload': 'Upload limit',\n  'transfer-speed-download': 'Download limit',\n  'transfer-speed-unlimited': 'Unlimited',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Save magnet link as torrent file',\n  'bt-auto-download-content': 'Automatically download magnet and torrent content',\n  'bt-force-encryption': 'BT force encryption',\n  'keep-seeding': 'Keep seeding until manually stopped',\n  'seed-ratio': 'Seed Ratio',\n  'seed-time': 'Seed Time',\n  'seed-time-unit': 'minutes',\n  'task-manage': 'Task Management',\n  'max-concurrent-downloads': 'Maximum active tasks',\n  'max-connection-per-server': 'Maximum connections per server',\n  'new-task-show-downloading': 'Automatically show downloading after adding task',\n  'no-confirm-before-delete-task': 'No confirmation is required before deleting task',\n  'continue': 'Continue',\n  'task-completed-notify': 'Notify after download is complete',\n  'auto-purge-record': 'Automatically purge download records when exiting app',\n  'ui': 'UI',\n  'appearance': 'Appearance',\n  'theme-auto': 'Auto',\n  'theme-light': 'Light',\n  'theme-dark': 'Dark',\n  'auto-hide-window': 'Auto Hide Window',\n  'run-mode': 'Run As',\n  'run-mode-standard': 'Standard Application',\n  'run-mode-tray': 'Tray Application',\n  'run-mode-hide-tray': 'Hide Tray Application',\n  'tray-speedometer': 'Show the real-time speed in the menu bar tray',\n  'show-progress-bar': 'Show download progress bar',\n  'language': 'Language',\n  'change-language': 'Change language',\n  'hide-app-menu': 'Hide App Menu (Windows & Linux Only)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Enable Proxy',\n  'proxy-bypass-input-tips': 'Bypass proxy settings for these Hosts and Domains, one per line',\n  'proxy-scope-download': 'Download',\n  'proxy-scope-update-app': 'Update Application',\n  'proxy-scope-update-trackers': 'Update Trackers',\n  'proxy-tips': 'View Proxy Manual',\n  'bt-tracker': 'Tracker Servers',\n  'bt-tracker-input-tips': 'Tracker servers, one per line',\n  'bt-tracker-tips': 'Recommended: ',\n  'sync-tracker-tips': 'Sync',\n  'auto-sync-tracker': 'Update tracker list every day automatically',\n  'port': 'Listen Ports',\n  'bt-port': 'BT Listen Port',\n  'dht-port': 'DHT Listen Port',\n  'security': 'Security',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC Listen Port',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'View RPC Secret Manual',\n  'developer': 'Developer',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Built-in aria2.conf path',\n  'app-log-path': 'App log path',\n  'download-session-path': 'Download session path',\n  'session-reset': 'Reset download session',\n  'session-reset-confirm': 'Are you sure you want to reset download session?',\n  'factory-reset': 'Factory Reset',\n  'factory-reset-confirm': 'Are you sure you want to revert to factory settings?',\n  'lab-warning': '⚠️ Enabling lab features may result in app crash or data loss, decide at you own risk!',\n  'download-protocol': 'Protocols',\n  'protocols-default-client': 'Set as the default client for the following protocols',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Extensions',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Provided by the community, ',\n  'baidu-exporter-help': 'Click here for usage',\n  'auto-update': 'Auto Update',\n  'auto-check-update': 'Automatically check for updates',\n  'last-check-update-time': 'Last checked for an update',\n  'not-saved': 'Preferences not saved',\n  'not-saved-confirm': 'The modified preferences will be lost, are you sure you want to leave?'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/subnav.js",
    "content": "export default {\n  'task-list': 'Tasks',\n  'preferences': 'Preferences'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/task.js",
    "content": "export default {\n  'active': 'Downloading',\n  'waiting': 'Waiting',\n  'stopped': 'Stopped',\n  'new-task': 'New Task',\n  'new-bt-task': 'New BT Task',\n  'open-file': 'Open Torrent File...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'One task url per line (supports magnet)',\n  'thunder-link-tips': 'Tip: Thunder links may not be downloadable after decoding',\n  'new-task-uris-required': 'Please enter at least one valid resource url',\n  'new-task-torrent-required': 'Please select a torrent file',\n  'file-name': 'Name',\n  'file-extension': 'Extension',\n  'file-size': 'Size',\n  'file-completed-size': 'Completed',\n  'selected-files-sum': 'Selected: {{selectedFilesCount}} files, total size {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Please select at least one file',\n  'task-gid': 'GID',\n  'task-name': 'Task Name',\n  'task-out': 'Rename',\n  'task-out-tips': 'Optional',\n  'task-split': 'Splits',\n  'task-dir': 'Save to',\n  'pause-task': 'Pause Task',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Authorization',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Error',\n  'task-piece': 'Piece',\n  'task-piece-length': 'Piece Size',\n  'task-num-pieces': 'Pieces',\n  'task-bittorrent-info': 'Torrent Info',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Creation Date',\n  'task-bittorrent-comment': 'Comment',\n  'task-progress-info': 'Progress',\n  'task-status': 'Status',\n  'task-num-seeders': 'Seeders',\n  'task-connections': 'Connections',\n  'task-file-size': 'Size',\n  'task-download-speed': 'Download Speed',\n  'task-upload-speed': 'Upload Speed',\n  'task-download-length': 'Downloaded',\n  'task-upload-length': 'Uploaded',\n  'task-ratio': 'Ratio',\n  'task-peer-host': 'Host',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Client',\n  'navigate-to-downloading': 'Navigate to Downloading',\n  'show-advanced-options': 'Advanced Options',\n  'copyright-warning': 'Copyright Warning',\n  'copyright-warning-message': 'The file you want to download may be copyrighted audio or video, please ensure that you have permission to access to it.',\n  'copyright-yes': 'Yes, I have permission',\n  'copyright-no': 'No, I don\\'t have permission',\n  'copyright-error-message': 'Failed to add task due to copyright issue',\n  'pause-task-success': 'Successfully paused task \"{{taskName}}\"',\n  'pause-task-fail': 'Failed to pause task \"{{taskName}}\"',\n  'resume-task': 'Resume Task',\n  'resume-task-success': 'Successfully resumed task \"{{taskName}}\"',\n  'resume-task-fail': 'Failed to resume task \"{{taskName}}\"',\n  'delete-task': 'Delete Task',\n  'delete-selected-tasks': 'Delete Selected Tasks',\n  'delete-task-confirm': 'Are you sure you want to remove download task \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Are you sure you want to remove {{count}} download tasks in batch?',\n  'delete-task-label': 'Delete with Files',\n  'delete-task-success': 'Successfully deleted task \"{{taskName}}\"',\n  'delete-task-fail': 'Failed to delete task \"{{taskName}}\"',\n  'remove-task-file-fail': 'Failed to delete task file(s), please delete them manually',\n  'remove-task-config-file-fail': 'Failed to delete task config file, please delete it manually',\n  'move-task-up': 'Move Task Up',\n  'move-task-down': 'Move Task Down',\n  'pause-all-task': 'Pause All Tasks',\n  'pause-all-task-success': 'Successfully paused all tasks',\n  'pause-all-task-fail': 'Failed to pause all tasks',\n  'resume-all-task': 'Resume All Tasks',\n  'resume-all-task-success': 'Successfully resumed all tasks',\n  'resume-all-task-fail': 'Failed to resume all tasks',\n  'select-all-task': 'Select All Tasks',\n  'clear-recent-tasks': 'Clear Recent Tasks',\n  'purge-record': 'Purge Task Record',\n  'purge-record-success': 'Successfully purged task records',\n  'purge-record-fail': 'Failed to purge task records',\n  'batch-delete-task-success': 'Successfully delete tasks in batch',\n  'batch-delete-task-fail': 'Failed to delete tasks in batch',\n  'refresh-list': 'Refresh Task List',\n  'no-task': 'There are no current tasks',\n  'copy-link': 'Copy Link',\n  'copy-link-success': 'Successfully copied link',\n  'remove-record': 'Remove Task Record',\n  'remove-record-confirm': 'Are you sure you want to remove download record for \"{{taskName}}\"?',\n  'remove-record-label': 'Delete with Files',\n  'remove-record-success': 'Successfully removed task record for \"{{taskName}}\"',\n  'remove-record-fail': 'Failed to remove task record for \"{{taskName}}\"',\n  'show-in-folder': 'Show Task In Folder',\n  'file-not-exist': 'Target file does not exist or has been deleted',\n  'file-path-error': 'File path error',\n  'opening-task-message': 'Opening \"{{taskName}}\" ...',\n  'get-task-name': 'Getting task name...',\n  'remaining-prefix': 'Remaining',\n  'select-torrent': 'Drag torrent file here, or click to select',\n  'task-detail-title': 'Task Details',\n  'task-info-dialog-title': '{{title}} Details',\n  'download-start-message': 'Started downloading {{taskName}}',\n  'download-pause-message': 'Paused downloading {{taskName}}',\n  'download-stop-message': 'Stopped downloading {{taskName}}',\n  'download-error-message': 'Error occurred when downloading {{taskName}}',\n  'download-complete-message': 'Completed downloading {{taskName}}',\n  'download-complete-notify': 'Download Completed',\n  'bt-download-complete-message': 'Completed downloading {{taskName}}, seeding',\n  'bt-download-complete-notify': 'BT Download Completed, seeding...',\n  'bt-download-complete-tips': 'Tips: You can stop a task to end its seeding',\n  'bt-stopping-seeding-tip': 'Stopping seeding, it will take some time to disconnect, please wait patiently',\n  'download-fail-message': 'Failed to download {{taskName}}',\n  'download-fail-notify': 'Download Failed'\n}\n"
  },
  {
    "path": "src/shared/locales/en-US/window.js",
    "content": "export default {\n  'reload': 'Reload',\n  'close': 'Close',\n  'minimize': 'Minimize',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Enter Full Screen',\n  'front': 'Bring All to Front'\n}\n"
  },
  {
    "path": "src/shared/locales/es/about.js",
    "content": "export default {\n  'engine-version': 'Versión del motor',\n  'license': 'Licencia',\n  'about': 'Acerca de',\n  'release': 'Versión',\n  'support': 'Soporte'\n}\n"
  },
  {
    "path": "src/shared/locales/es/app.js",
    "content": "export default {\n  'task-list': 'Tareas',\n  'add-task': 'Añadir tareas',\n  'about': 'Acerca de Motrix',\n  'preferences': 'Preferencias...',\n  'check-for-updates': 'Comprobar actualizaciones...',\n  'check-updates-now': 'Comprobar ahora',\n  'checking-for-updates': 'Comprobando actualizaciones...',\n  'check-for-updates-title': 'Comprobar actualizaciones',\n  'update-available-message': 'Hay una nueva versión de Motrix, ¿Actualizar ahora?',\n  'update-not-available-message': '¡Esta es la última versión!',\n  'update-downloaded-message': 'Listo para instalar...',\n  'update-error-message': 'Error al actualizar',\n  'engine-damaged-message': 'El motor está dañado, por favor reinstalar :(',\n  'engine-missing-message': 'No se encuentra el motor, por favor reinstalar :(',\n  'system-error-title': 'Error del sistema',\n  'system-error-message': 'La aplicación falló al iniciar: {{message}}',\n  'hide': 'Ocultar Motrix',\n  'hide-others': 'Ocultar otros',\n  'unhide': 'Mostrar Todo',\n  'show': 'Mostrar Motrix',\n  'quit': 'Salir de Motrix',\n  'under-development-message': 'Disculpa, esta función está en desarrollo...',\n  'yes': 'Sí',\n  'no': 'No',\n  'save': 'Guardar',\n  'reset': 'Reiniciar',\n  'cancel': 'Cancelar',\n  'submit': 'Enviar',\n  'gt1d': '> 1 día',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/es/edit.js",
    "content": "export default {\n  'undo': 'Deshacer',\n  'redo': 'Rehacer',\n  'cut': 'Cortar',\n  'copy': 'Copiar',\n  'paste': 'Pegar',\n  'delete': 'Eliminar',\n  'select-all': 'Seleccionar Todo'\n}\n"
  },
  {
    "path": "src/shared/locales/es/help.js",
    "content": "export default {\n  'official-website': 'Sitio web de Motrix',\n  'manual': 'Manual',\n  'release-notes': 'Notas de la versión...',\n  'report-problem': 'Reportar un problema',\n  'toggle-dev-tools': 'Alternar las herramientas de desarrollo'\n}\n"
  },
  {
    "path": "src/shared/locales/es/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/es/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Archivo',\n  'task': 'Tarea',\n  'edit': 'Editar',\n  'window': 'Ventana',\n  'help': 'Ayuda'\n}\n"
  },
  {
    "path": "src/shared/locales/es/preferences.js",
    "content": "export default {\n  'basic': 'Básico',\n  'advanced': 'Avanzado',\n  'lab': 'Lab',\n  'save': 'Guardar y Aplicar',\n  'save-success-message': 'Preferencias guardadas exitosamente',\n  'save-fail-message': 'Hubo un error al guardar tus preferencias',\n  'discard': 'Descartar',\n  'startup': 'Inicio',\n  'open-at-login': 'Abrir al iniciar sesión',\n  'keep-window-state': 'Mantener el tamaño y la posición de la ventana al salir',\n  'auto-resume-all': 'Reanudar automáticamente todas las tareas sin finalizar',\n  'default-dir': 'Ruta por defecto',\n  'mas-default-dir-tips': 'Debido a las restricciones de la tienda de aplicaciones, la ruta por defecto se recomienda que sea ~/Downloads',\n  'transfer-settings': 'Transferencia',\n  'transfer-speed-upload': 'Límite de subida',\n  'transfer-speed-download': 'Límite de bajada',\n  'transfer-speed-unlimited': 'Ilimitado',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Guardar enlace magnet como archivo torrent',\n  'bt-auto-download-content': 'Descargar automáticamente el contenido de magnet y torrent',\n  'bt-force-encryption': 'Forzar encriptación BT',\n  'keep-seeding': 'Siga sembrando hasta detenerlo manualmente',\n  'seed-ratio': 'Proporción de semillas',\n  'seed-time': 'Tiempo de semilla',\n  'seed-time-unit': 'minutos',\n  'task-manage': 'Gestión de tareas',\n  'max-concurrent-downloads': 'Tareas máximas activas',\n  'max-connection-per-server': 'Conexiones máximas por servidor',\n  'new-task-show-downloading': 'Mostrar automáticamente la descarga después de añadir una tarea',\n  'no-confirm-before-delete-task': 'No se requiere confirmación antes de eliminar la tarea',\n  'continue': 'Continuar',\n  'task-completed-notify': 'Notificar después de que la descarga se complete',\n  'auto-purge-record': 'Eliminar automáticamente el registro de descargas al salir',\n  'ui': 'UI',\n  'appearance': 'Apariencia',\n  'theme-auto': 'Auto',\n  'theme-light': 'Claro',\n  'theme-dark': 'Oscuro',\n  'auto-hide-window': 'Ocultar automáticamente ventanas',\n  'run-mode': 'Correr como',\n  'run-mode-standard': 'Aplicación estándar',\n  'run-mode-tray': 'Aplicación de bandeja',\n  'run-mode-hide-tray': 'Ocultar aplicación de bandeja',\n  'tray-speedometer': 'La bandeja de la barra de menú muestra la velocidad en tiempo real',\n  'show-progress-bar': 'Mostrar la barra de progreso de descarga',\n  'language': 'Idioma',\n  'change-language': 'Cambiar Idioma',\n  'hide-app-menu': 'Ocultar el menú (Solo Windows y Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Activar Proxy',\n  'proxy-bypass-input-tips': 'Omitir la configuración del proxy para estos hosts y dominios, uno por línea',\n  'proxy-scope-download': 'Descargar',\n  'proxy-scope-update-app': 'Actualizar aplicación',\n  'proxy-scope-update-trackers': 'Actualizar Trackers',\n  'proxy-tips': 'Ver manual para Proxy',\n  'bt-tracker': 'Servidores de rastreadores',\n  'bt-tracker-input-tips': 'Servidores de rastreadores, uno por línea',\n  'bt-tracker-tips': 'Recomendado: ',\n  'sync-tracker-tips': 'Sincronizar',\n  'auto-sync-tracker': 'Actualice la lista de rastreadores todos los días automáticamente',\n  'port': 'Puertos de Escucha',\n  'bt-port': 'Puerto de escucha BT',\n  'dht-port': 'Puerto de escucha DHT',\n  'security': 'Seguridad',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Puerto de Escucha RPC',\n  'rpc-secret': 'Clave RPC',\n  'rpc-secret-tips': 'Ver manual de la clave RPC',\n  'developer': 'Desarrollador',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Falsear Agente de Usuario',\n  'aria2-conf-path': 'Ruta incorporada de aria2.conf',\n  'app-log-path': 'Ruta del registro',\n  'download-session-path': 'Ruta de descarga de la sesión',\n  'factory-reset': 'Restauración de fábrica',\n  'factory-reset-confirm': '¿Estás seguro que quieres restaurar de fábrica?',\n  'lab-warning': '¡Habilitar las característticas \"Lab\" puede resultar en errores y pérdida de datos!',\n  'download-protocol': 'Protocolos',\n  'protocols-default-client': 'Establecer como cliente por defecto de los siguientes protocolos',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Extensiones',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Proporcionados por la comunidad, ',\n  'baidu-exporter-help': 'Presiona aqui para ver el uso',\n  'auto-update': 'Auto-actualizar',\n  'auto-check-update': 'Revisar automáticamente por actualizaciones',\n  'last-check-update-time': 'Última revisión de actualizaciones',\n  'not-saved': 'Preferencias no guardadas',\n  'not-saved-confirm': 'Las preferencias cambiadas se perderán, ¿está seguro de irse?'\n}\n"
  },
  {
    "path": "src/shared/locales/es/subnav.js",
    "content": "export default {\n  'task-list': 'Tareas',\n  'preferences': 'Preferencias'\n}\n"
  },
  {
    "path": "src/shared/locales/es/task.js",
    "content": "export default {\n  'active': 'Descargando',\n  'waiting': 'Esperando',\n  'stopped': 'Detenida',\n  'new-task': 'Nueva Tarea',\n  'new-bt-task': 'Nueva Tarea BT',\n  'open-file': 'Abrir archivo de Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Una url de tarea por línea (soporta magnet)',\n  'thunder-link-tips': 'Tip: Es posible que los enlaces Thunder no se puedan descargar después de la decodificación.',\n  'new-task-uris-required': 'Por favor, introduzca al menos una url de recurso válida',\n  'new-task-torrent-required': 'Seleccione un archivo torrent',\n  'file-name': 'Nombre',\n  'file-extension': 'Extensión',\n  'file-size': 'Tamaño',\n  'file-completed-size': 'Terminado',\n  'selected-files-sum': 'Seleccionado: {{selectedFilesCount}} archivos, tamaño total {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Seleccione al menos un archivo',\n  'task-gid': 'GID',\n  'task-name': 'Nombre de la tarea',\n  'task-out': 'Renombrar',\n  'task-out-tips': 'Opcional',\n  'task-split': 'Dividir',\n  'task-dir': 'Guardar en',\n  'pause-task': 'Pausar Tarea',\n  'task-ua': 'UA',\n  'task-user-agent': 'Agente de Usuario',\n  'task-authorization': 'Autorización',\n  'task-referer': 'Referente',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Error',\n  'task-piece': 'Trozo',\n  'task-piece-length': 'Tamaño de pieza',\n  'task-num-pieces': 'Piezas',\n  'task-bittorrent-info': 'Información de Torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Fecha de creación',\n  'task-bittorrent-comment': 'Comentario',\n  'task-progress-info': 'Progreso',\n  'task-status': 'Estado',\n  'task-num-seeders': 'Semillas',\n  'task-connections': 'Conexiones',\n  'task-file-size': 'Tamaño',\n  'task-download-speed': 'Velocidad de Descarga',\n  'task-upload-speed': 'Velocidad de subida',\n  'task-download-length': 'Descargado',\n  'task-upload-length': 'Subido',\n  'task-ratio': 'Proporción',\n  'task-peer-host': 'Anfitrión',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Cliente',\n  'navigate-to-downloading': 'Navegar a Descargas',\n  'show-advanced-options': 'Opciones avanzadas',\n  'copyright-warning': 'Advertencia sobre derechos de autor',\n  'copyright-warning-message': 'El archivo que desea descargar puede tener derechos de autor de audio o video, por favor asegúrese de que tiene permiso para acceder a él.',\n  'copyright-yes': 'Sí, tengo permiso',\n  'copyright-no': 'No, no tengo permiso.',\n  'copyright-error-message': 'No se pudo agregar una tarea debido a un problema de derechos de autor',\n  'pause-task-success': 'Se ha pausado la tarea \"{{taskName}}\"',\n  'pause-task-fail': 'Hubo un fallo al pausar la tarea \"{{taskName}}\"',\n  'resume-task': 'Resumir tarea',\n  'resume-task-success': 'Se ha reanudado la tarea \"{{taskName}}\"',\n  'resume-task-fail': 'Hubo un error al resumir la tarea \"{{taskName}}\"',\n  'delete-task': 'Eliminar tarea',\n  'delete-selected-tasks': 'Eliminar tareas seleccionadas',\n  'delete-task-confirm': '¿Está seguro que desea eliminar la tarea \"{{taskName}}\"?',\n  'batch-delete-task-confirm': '¿Está seguro de que desea eliminar {{count}} tareas de descarga en lote?',\n  'delete-task-label': 'Eliminar con archivos',\n  'delete-task-success': 'Tarea eliminada exitosamente \"{{taskName}}\"',\n  'delete-task-fail': 'Hubo un error al eliminar la tarea \"{{taskName}}\"',\n  'remove-task-file-fail': 'No se han eliminado los archivos de tareas, por favor, elimínelos manualmente.',\n  'remove-task-config-file-fail': 'No se ha podido eliminar el archivo de configuración de la tarea, por favor, elimínelo manualmente.',\n  'move-task-up': 'Desplazar tarea hacia arriba',\n  'move-task-down': 'Desplazar tarea hacia abajo',\n  'pause-all-task': 'Pausar todas las tareas',\n  'pause-all-task-success': 'Se pausaron todas las tareas exitosamente',\n  'pause-all-task-fail': 'Hubo un error al pausar todas las tareas',\n  'resume-all-task': 'Reanudar todas las tareas',\n  'resume-all-task-success': 'Se reanudaron exitosamente todas las tareas',\n  'resume-all-task-fail': 'Hubo un error al reanudar todas las tareas',\n  'select-all-task': 'Seleccionar toda la tarea',\n  'clear-recent-tasks': 'Limpiar tareas recientes',\n  'purge-record': 'Registros de tareas purgados con éxito',\n  'purge-record-success': 'Se eliminaron los registros de tareas de manera exitosa',\n  'purge-record-fail': 'No se pudieron eliminar los registros de tareas',\n  'batch-delete-task-success': 'Eliminar con éxito las tareas en lote',\n  'batch-delete-task-fail': 'Error al eliminar tareas en lote',\n  'refresh-list': 'Actualizar lista de tareas',\n  'no-task': 'No hay tareas actuales',\n  'copy-link': 'Copiar enlace',\n  'copy-link-success': 'Enlace copiado exitosamente',\n  'remove-record': 'Eliminar tarea',\n  'remove-record-confirm': '¿Está seguro que desea eliminar la tarea \"{{taskName}}\"?',\n  'remove-record-label': 'Eliminar con archivos',\n  'remove-record-success': 'Se ha removido exitosamente la tarea \"{{taskName}}\"',\n  'remove-record-fail': 'Hubo un fallo al eliminar la tarea \"{{taskName}}\"',\n  'show-in-folder': 'Mostrar la carpeta de la tarea',\n  'file-not-exist': 'El archivo objetivo no existe o ha sido eliminado',\n  'file-path-error': 'Error en la ruta del archivo',\n  'opening-task-message': 'Abriendo \"{{taskName}}\"...',\n  'get-task-name': 'Obteniendo el nombre de la tarea...',\n  'remaining-prefix': 'Faltante',\n  'select-torrent': 'Arrastrar un archivo Torrent aquí o clic para seleccionar',\n  'task-info-dialog-title': 'Detalles de {{title}}',\n  'download-start-message': 'Se inicio la descarga de {{taskName}}',\n  'download-pause-message': 'Se pausó la descarga de {{taskName}}',\n  'download-stop-message': 'Se detuvo la descarga de {{taskName}}',\n  'download-error-message': 'Ha ocurrido un error al descargar {{taskName}}',\n  'download-complete-message': 'Se termino de descargar {{taskName}}',\n  'download-complete-notify': 'Descarga completada',\n  'bt-download-complete-message': 'Descarga completada {{taskName}}, compartiendo',\n  'bt-download-complete-notify': 'Descarga BT completa, compartiendo...',\n  'bt-download-complete-tips': 'Consejo: Puede detener una tarea para dejar de compartir',\n  'bt-stopping-seeding-tip': 'Deteniendo la siembra, tomará un tiempo desconectarse, por favor espere...',\n  'download-fail-message': 'No se pudo descargar {{taskName}}',\n  'download-fail-notify': 'Descarga fallida'\n}\n"
  },
  {
    "path": "src/shared/locales/es/window.js",
    "content": "export default {\n  'reload': 'Recargar',\n  'close': 'Cerrar',\n  'minimize': 'Minimizar',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Entrar a Pantalla Completa',\n  'front': 'Pasar Todo al frente'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/about.js",
    "content": "export default {\n  'engine-version': 'نسخهٔ موتور',\n  'license': 'مجوز',\n  'about': 'دربارهٔ ما',\n  'release': 'نسخه‌های منتشر شده',\n  'support': 'حمایت'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/app.js",
    "content": "export default {\n  'task-list': 'وظایف',\n  'add-task': 'افزودن وظیفه',\n  'about': 'دربارهٔ ماتریکس',\n  'preferences': 'ترجیحات...',\n  'check-for-updates': 'بررسی برای به‌روز رسانی...',\n  'check-updates-now': 'اکنون بررسی کن',\n  'checking-for-updates': 'درحال بررسی برای به‌روز رسانی...',\n  'check-for-updates-title': 'بررسی برای به‌روز رسانی',\n  'update-available-message': 'نسخهٔ جدید ماتریکس در دسترس است. به‌روز رسانی شود؟',\n  'update-not-available-message': 'شما به‌روز هستید!',\n  'update-downloaded-message': 'آمادهٔ نصب...',\n  'update-error-message': 'خطا در به‌روز رسانی',\n  'engine-damaged-message': 'موتور صدمه دیده است! لطفا دوباره نصب کنید : (',\n  'engine-missing-message': 'موتور گم شده است! لطفا دوباره نصب کنید : (',\n  'system-error-title': 'خطای سیستم',\n  'system-error-message': 'برنامه نتوانست شروع به کار کند: {{message}}',\n  'hide': 'ماتریکس را پنهان کن',\n  'hide-others': 'بقیه را پنهان کن',\n  'unhide': 'همه را نشان بده',\n  'show': 'ماتریکس را نشان بده',\n  'quit': 'از ماتریکس خارج شو',\n  'under-development-message': 'متأسفیم، این قابلیت در حال توسعه است...',\n  'yes': 'بله',\n  'no': 'خیر',\n  'save': 'ذخیره',\n  'reset': 'دور انداختن',\n  'cancel': 'لغو',\n  'submit': 'ثبت',\n  'gt1d': '> یک روز',\n  'hour': 'ساعت',\n  'minute': 'دقیقه',\n  'second': 'ثانیه'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/edit.js",
    "content": "export default {\n  'undo': 'برگردان',\n  'redo': 'انجام دوباره',\n  'cut': 'برش',\n  'copy': 'رونوشت',\n  'paste': 'جای‌گذاری',\n  'delete': 'حذف',\n  'select-all': 'گزینش همه'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/help.js",
    "content": "export default {\n  'official-website': 'سایت ماتریکس',\n  'manual': 'کتابچهٔ راهنما',\n  'release-notes': 'یادداشت‌های انتشار...',\n  'report-problem': 'گزارش مشکل',\n  'toggle-dev-tools': 'تغییر حالت ابزارهای توسعه‌دهنده'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/fa/menu.js",
    "content": "export default {\n  'app': 'ماتریکس',\n  'file': 'پرونده',\n  'task': 'وظیفه',\n  'edit': 'ویرایش',\n  'window': 'پنجره',\n  'help': 'راهنما'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/preferences.js",
    "content": "export default {\n  'basic': 'پایه',\n  'advanced': 'پیشرفته',\n  'lab': 'آزمایشگاه',\n  'save': 'ذخیره و اعمال',\n  'save-success-message': 'ترجیحات با موفقیت ذخیره شد',\n  'save-fail-message': 'ذخیرهٔ ترجیحات شکست خورد',\n  'discard': 'دور انداختن',\n  'startup': 'شروع برنامه',\n  'open-at-login': 'گشودن هنگام ورود به سیستم',\n  'keep-window-state': 'نگه‌داشتن اندازه و موقعیت پنجره هنگام خروج',\n  'auto-resume-all': 'از سرگیری خودکار همه وظایف ناتمام',\n  'default-dir': 'مسیر پیش‌فرض',\n  'mas-default-dir-tips': 'بخاطر محدودیت‌های دسترسی سندباکس از اپ استور، پیشنهاد می‌شود شاخه پیش‌فرض دانلود بر روی ~/Downloads تنظیم شده باشد.',\n  'transfer-settings': 'انتقال',\n  'transfer-speed-upload': 'محدودیت بارگذاری',\n  'transfer-speed-download': 'محدودیت بارگیری',\n  'transfer-speed-unlimited': 'نامحدود',\n  'bt-settings': 'بیت‌تورنت',\n  'bt-save-metadata': 'ذخیره پیوند مگنت به عنوان پرونده تورنت',\n  'bt-auto-download-content': 'بارگیری خودکار مگنت و محتوای تورنت',\n  'bt-force-encryption': 'رمزگذاری اجباری ب‌ت',\n  'keep-seeding': 'بذرپاشی را تا هنگامی که به صورت دستی قطع نشده، ادامه بده',\n  'seed-ratio': 'نسبت دانه',\n  'seed-time': 'زمان دانه',\n  'seed-time-unit': 'دقیقه',\n  'task-manage': 'مدیریت وظایف',\n  'max-concurrent-downloads': 'بیشینهٔ وظایف فعال',\n  'max-connection-per-server': 'بیشینهٔ اتصال برای هر سرور',\n  'new-task-show-downloading': 'بعد افزودن وظیفه به صورت خودکار بارگیری را نشان بده',\n  'no-confirm-before-delete-task': 'قبل از حذف وظیفه نیازی به تأیید نیست',\n  'continue': 'ادامه',\n  'task-completed-notify': 'پس از تکمیل بارگیری اطلاع رسانی کن',\n  'auto-purge-record': 'به صورت خودکار سابقهٔ بارگیری را هنگام خروج پاک کن',\n  'ui': 'رابط کاربری',\n  'appearance': 'ظاهر',\n  'theme-auto': 'خودکار',\n  'theme-light': 'روشن',\n  'theme-dark': 'تاریک',\n  'auto-hide-window': 'پنهان کردن خودکار پنجره',\n  'run-mode': 'اجرا به عنوان',\n  'run-mode-standard': 'برنامه استاندارد',\n  'run-mode-tray': 'برنامه سینی',\n  'run-mode-hide-tray': 'مخفی کردن برنامه سینی',\n  'tray-speedometer': 'سینی نوار منو سرعت زمان واقعی را نشان می‌دهد',\n  'show-progress-bar': 'نمایش نوار پیشرفت دانلود',\n  'language': 'زبان',\n  'change-language': 'تغییر زبان',\n  'hide-app-menu': 'پنهان کردن منوی برنامه (تنها در ویندوز و لینوکس)',\n  'proxy': 'پیشکار',\n  'enable-proxy': 'فعال کردن پیشکار',\n  'proxy-bypass-input-tips': 'تنظیمات پیشکار را برای این میزبان‌ها و دامنه‌ها استفاده نکن، یکی در هر خط',\n  'proxy-scope-download': 'دانلود',\n  'proxy-scope-update-app': 'به‌روزرسانی برنامه',\n  'proxy-scope-update-trackers': 'به‌روزرسانی پیگیر‌ها',\n  'proxy-tips': 'مشاهده راهنمای پیشکار',\n  'bt-tracker': 'سرورهای ردیاب',\n  'bt-tracker-input-tips': 'سرورهای ردیاب، یکی در هر خط',\n  'bt-tracker-tips': 'توصیه شده: ',\n  'sync-tracker-tips': 'همگام‌سازی',\n  'auto-sync-tracker': 'لیست ردیاب را هر روز به طور خودکار به‌روز کنید',\n  'port': 'درگاه',\n  'bt-port': 'درگاه BT',\n  'dht-port': 'درگاه DHT',\n  'security': 'امنیت',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'پورت گوش دادن به RPC',\n  'rpc-secret': 'رمز RPC',\n  'rpc-secret-tips': 'مشاهده راهنمای رمز RPC',\n  'developer': 'توسعه‌دهنده',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'جعل عامل کاربر',\n  'aria2-conf-path': 'مسیر aria2.conf درونی',\n  'app-log-path': 'مسیر گزارش برنامه',\n  'download-session-path': 'مسیر نشست بارگیری',\n  'session-reset': 'بازنشانی نشست بارگیری',\n  'session-reset-confirm': 'آیا برای بازنشانی نشست بارگیری مطمئنید؟',\n  'factory-reset': 'بازنشانی به تنظیمات کارخانه',\n  'factory-reset-confirm': 'آیا برای بازنشانی تنظیمات کارخانه مطمئنید؟',\n  'lab-warning': '⚠️ فعال سازی قابلیت آزمایشگاه ممکن است باعث خرابی برنامه یا از دست دادن داده شود، با مسئولیت خود تصمیم بگیرید!',\n  'download-protocol': 'شیوه‌نامه',\n  'protocols-default-client': 'تنظیم به عنوان کارخواه پیش‌گزیده برای شیوه‌نامه‌های زیر',\n  'protocols-magnet': 'آهن‌ربا [ magnet:// ]',\n  'protocols-thunder': 'تندر [ thunder:// ]',\n  'browser-extensions': 'افزونه‌ها',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'تهیه شده توسط انجمن، ',\n  'baidu-exporter-help': 'برای استفاده اینجا کلیک کنید',\n  'auto-update': 'به‌روز رسانی خودکار',\n  'auto-check-update': 'به صورت خودکار برای به‌روز رسانی بررسی کن',\n  'last-check-update-time': 'آخرین زمان بررسی برای به‌روز رسانی',\n  'not-saved': 'تنظیمات برگزیده ذخیره نشد',\n  'not-saved-confirm': 'تنظیمات برگزیده تغییر یافته از بین خواهند رفت، آیا مطمئن هستید که می روید؟'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/subnav.js",
    "content": "export default {\n  'task-list': 'وظایف',\n  'preferences': 'ترجیحات'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/task.js",
    "content": "export default {\n  'active': 'در حال بارگیری',\n  'waiting': 'در انتظار',\n  'stopped': 'متوقف شد',\n  'new-task': 'وظیفهٔ جدید',\n  'new-bt-task': 'وظیفهٔ بیت‌تورنت جدید',\n  'open-file': 'بازکردن پروندهٔ تورنت...',\n  'uri-task': 'آدرس اینترنتی',\n  'torrent-task': 'تورنت',\n  'uri-task-tips': 'یک آدرس در هر خط(از مگنت پشتیبانی می‌شود)',\n  'thunder-link-tips': 'نکته: پیوند‌های تاندر ممکن هست بعد کدگشایی قابل بارگیری نباشند',\n  'new-task-uris-required': 'لطفاً حداقل یک آدرس معتبر وارد کنید',\n  'new-task-torrent-required': 'لطفاً یک پروندهٔ تورنت را انتخاب کنید',\n  'file-name': 'نام',\n  'file-extension': 'پسوند',\n  'file-size': 'اندازه',\n  'file-completed-size': 'تکمیل شده',\n  'selected-files-sum': 'گزیده: {{selectedFilesCount}} پرونده، اندازه کل {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'لطفاً حداقل یک پرونده را انتخاب کنید',\n  'task-gid': 'GID',\n  'task-name': 'نام وظیفه',\n  'task-out': 'تغییرنام',\n  'task-out-tips': 'اختیاری',\n  'task-split': 'برش‌ها',\n  'task-dir': 'ذخیره در',\n  'pause-task': 'مکث',\n  'task-ua': 'UA',\n  'task-user-agent': 'عامل کاربر',\n  'task-authorization': 'مجوز',\n  'task-referer': 'ارجاع دهنده',\n  'task-cookie': 'کوکی',\n  'task-proxy': 'پیشکار',\n  'task-error-info': 'خطا',\n  'task-piece': 'قطعه',\n  'task-piece-length': 'اندازه قطعه',\n  'task-num-pieces': 'قطعات',\n  'task-bittorrent-info': 'اطلاعات تورنت',\n  'task-info-hash': 'هش',\n  'task-bittorrent-creation-date': 'تاریخ ایجاد',\n  'task-bittorrent-comment': 'توضیح',\n  'task-progress-info': 'پیشرفت',\n  'task-status': 'وضعیت',\n  'task-num-seeders': 'بذرپاش',\n  'task-connections': 'اتصالات',\n  'task-file-size': 'اندازه',\n  'task-download-speed': 'سرعت بارگیری',\n  'task-upload-speed': 'سرعت بارگذاری',\n  'task-download-length': 'بارگیری شد',\n  'task-upload-length': 'بارگذاری شد',\n  'task-ratio': 'نسبت',\n  'task-peer-host': 'میزبان',\n  'task-peer-ip': 'آی‌پی',\n  'task-peer-client': 'کارخواه',\n  'navigate-to-downloading': 'برو به دانلودها',\n  'show-advanced-options': 'گزینه‌های پیشرفته',\n  'copyright-warning': 'هشدار حق تکثیر',\n  'copyright-warning-message': 'پرونده‌ای که می‌خواهید بارگیری کنید ممکن است صوت یا ویدیوی تحت حمایت قانون حق تکثیر باشد، مطمئن شوید که اجازه دسترسی به آن را دارید.',\n  'copyright-yes': 'بله، من اجازه دارم',\n  'copyright-no': 'خیر، من اجازه ندارم',\n  'copyright-error-message': 'به دلیل حق تکثیر، افزودن وظیفه انجام نشد',\n  'pause-task-success': 'توقف وظیفه \"{{taskName}}\" با موفقیت انجام شد',\n  'pause-task-fail': 'توقف وظیفه \"{{taskName}}\" شکست خورد',\n  'resume-task': 'از سرگیری وظیفه',\n  'resume-task-success': 'از سرگیری وظیفه \"{{taskName}}\" با موفقیت انجام شد',\n  'resume-task-fail': 'از سرگیری وظیفه \"{{taskName}}\" شکست خورد',\n  'delete-task': 'حذف وظیفه',\n  'delete-selected-tasks': 'وظایف گزیده را حذف کن',\n  'delete-task-confirm': 'آیا از حذف وظیفه \"{{taskName}}\" مطمئنید؟',\n  'batch-delete-task-confirm': 'آیا از حذف {{count}} وظیفه به صورت دسته جمعی مطمئنید؟',\n  'delete-task-label': 'حذف به همراه پرونده‌ها',\n  'delete-task-success': 'حذف وظیفه \"{{taskName}}\" با موفقیت انجام شد',\n  'delete-task-fail': 'حذف وظیفه \"{{taskName}}\" شکست خورد',\n  'remove-task-file-fail': 'حذف وظیفه(ها) شکست خورد، لطفاً به صورت دستی حذفشان کنید',\n  'remove-task-config-file-fail': 'حذف پروندهٔ پیکربندی وظیفه شکست خورد، لطفاً به صورت دستی حذفشان کنید',\n  'move-task-up': 'جابجایی وظیفه به بالا',\n  'move-task-down': 'جابجایی وظیفه به پایین',\n  'pause-all-task': 'توقف همهٔ وظایف',\n  'pause-all-task-success': 'توقف همهٔ وظایف با موفقیت انجام شد',\n  'pause-all-task-fail': 'توقف همهٔ وظایف شکست خورد',\n  'resume-all-task': 'از سرگیری همهٔ وظایف',\n  'resume-all-task-success': 'از سرگیری همهٔ وظایف با موفقیت انجام شد',\n  'resume-all-task-fail': 'از سرگیری همهٔ وظایف شکست خورد',\n  'select-all-task': 'انتخاب همه وظایف',\n  'clear-recent-tasks': 'پاک کردن وظایف اخیر',\n  'purge-record': 'پاک کردن سابقهٔ وظایف',\n  'purge-record-success': 'پاک کردن سابقهٔ وظایف با موفقیت انجام شد',\n  'purge-record-fail': 'پاک کردن سابقهٔ وظایف شکست خورد',\n  'batch-delete-task-success': 'حذف دسته جمعی وظایف با موفقیت انجام شد',\n  'batch-delete-task-fail': 'حذف دسته جمعی وظایف شکست خورد',\n  'refresh-list': 'تازه کردن لیست وظایف',\n  'no-task': 'فعلا وظیفه‌ای وجود ندارد',\n  'copy-link': 'رونوشت از پیوند',\n  'copy-link-success': 'رونوشت از پیوند با موفقیت انجام شد',\n  'remove-record': 'پاک کردن سابقهٔ وظیفه',\n  'remove-record-confirm': 'آیا از حذف سابقه \"{{taskName}}\" مطمئنید؟',\n  'remove-record-label': 'حذف به همراه پرونده‌ها',\n  'remove-record-success': 'پاک کردن سابقهٔ وظیفه \"{{taskName}}\" با موفقیت انجام شد',\n  'remove-record-fail': 'پاک کردن سابقهٔ وظیفه \"{{taskName}}\" شکست خورد',\n  'show-in-folder': 'نمایش وظیفه در پوشه',\n  'file-not-exist': 'پروندهٔ هدف وجود ندارد یا حذف شده است',\n  'file-path-error': 'خطای مسیر پرونده',\n  'opening-task-message': 'گشودن \"{{taskName}}\" ...',\n  'get-task-name': 'گرفتن نام وظیفه...',\n  'remaining-prefix': 'باقیمانده',\n  'select-torrent': 'پروندهٔ تورنت را اینجا بکشید، یا برای گزینش کلیک کنید',\n  'task-info-dialog-title': '{{title}} جزئیات',\n    'download-start-message': 'بارگیری {{taskName}} شروع شد',\n  'download-pause-message': 'مکث در بارگیری {{taskName}}',\n  'download-stop-message': 'بارگیری {{taskName}} متوقف شد',\n  'download-error-message': 'هنگام بارگیری {{taskName}} خطای رخ داد',\n  'download-complete-message': 'بارگیری {{taskName}} تکمیل شد',\n  'download-complete-notify': 'بارگیری تکمیل شد',\n  'bt-download-complete-message': 'بارگیری {{taskName}} تکمیل شد، بذرپاشی',\n  'bt-download-complete-notify': 'بارگیری بیت‌تورنت تکمیل شد، بذرپاشی...',\n  'bt-download-complete-tips': 'نکات: شما می‌توانید با توقف یک وظیفه، به بذرپاشی آن را پایان دهید',\n  'bt-stopping-seeding-tip': 'متوقف کردن بذرپاشی، قطع ارتباط مدتی طول خواهد کشید، لطفاً صبر کنید ...',\n  'download-fail-message': 'بارگیری {{taskName}} شکست خورد',\n  'download-fail-notify': 'بارگیری شکست خورد'\n}\n"
  },
  {
    "path": "src/shared/locales/fa/window.js",
    "content": "export default {\n  'reload': 'بارگزاری مجدد',\n  'close': 'بستن',\n  'minimize': 'کمینه',\n  'zoom': 'بزرگنمایی',\n  'toggle-fullscreen': 'حالت تمام صفحه',\n  'front': 'همه را جلو بیار'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/about.js",
    "content": "export default {\n  'engine-version': 'Version du moteur',\n  'license': 'Licence',\n  'about': 'À Propos',\n  'release': 'Release',\n  'support': 'Support'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/app.js",
    "content": "export default {\n  'task-list': 'Tâches',\n  'add-task': 'Ajouter une tâche',\n  'about': 'À Propos de Motrix',\n  'preferences': 'Préférences...',\n  'check-for-updates': 'Vérifier les mises à jour...',\n  'check-updates-now': 'Vérifier maintenant',\n  'checking-for-updates': 'Vérification des mises à jour ...',\n  'check-for-updates-title': 'Vérifier les mises à jour',\n  'update-available-message': 'Une nouvelle version de Motrix est disponible, mise à jour maintenant?',\n  'update-not-available-message': 'Vous êtes à jour!',\n  'update-downloaded-message': 'Prêt à installer...',\n  'update-error-message': 'Erreur de mise à jour',\n  'engine-damaged-message': 'Le moteur est endommagé, veuillez réinstaller : (',\n  'engine-missing-message': 'Le moteur est manquant, veuillez réinstaller : (',\n  'system-error-title': 'Erreur système',\n  'system-error-message': 'L\\'application n\\'a pas pu démarrer: {{message}}',\n  'hide': 'Cacher Motrix',\n  'hide-others': 'Cacher les autres',\n  'unhide': 'Tout montrer',\n  'show': 'Montrer Motrix',\n  'quit': 'Quitter Motrix',\n  'under-development-message': 'Désolé, cette fonctionnalité est en cours de développement...',\n  'yes': 'Oui',\n  'no': 'Non',\n  'save': 'Sauvegarder',\n  'reset': 'Jeter',\n  'cancel': 'Annuler',\n  'submit': 'Envoyer',\n  'gt1d': '> 1 jour',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/edit.js",
    "content": "export default {\n  'undo': 'Annuler la dernière action',\n  'redo': 'Rétablir la dernière action',\n  'cut': 'Couper',\n  'copy': 'Copier',\n  'paste': 'Coller',\n  'delete': 'Supprimer',\n  'select-all': 'Tout sélectionner'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/help.js",
    "content": "export default {\n  'official-website': 'Site web de Motrix ',\n  'manual': 'Manuel',\n  'release-notes': 'Notes de version...',\n  'report-problem': 'Signaler un problème',\n  'toggle-dev-tools': 'Activer les outils pour développeurs'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/fr/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Fichier',\n  'task': 'Tâche',\n  'edit': 'Editer',\n  'window': 'Fenêtre',\n  'help': 'Aide'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/preferences.js",
    "content": "export default {\n  'basic': 'Basique',\n  'advanced': 'Avancé',\n  'lab': 'Labo',\n  'save': 'Sauver et appliquer',\n  'save-success-message': 'Enregistrer les préférences avec succès',\n  'save-fail-message': 'La sauvegarde des préférences a échoué',\n  'discard': 'Annuler les changement',\n  'startup': 'Démarrage',\n  'open-at-login': 'Ouvrir à la connexion',\n  'keep-window-state': 'Restaurez la taille et la position de la fenêtre',\n  'auto-resume-all': 'Reprendre les tâches non terminées',\n  'default-dir': 'Répertoire par défaut',\n  'mas-default-dir-tips': 'En raison des restrictions d\\'autorisations du bac à sable de l\\'App Store, il est recommandé de définir le répertoire de téléchargement par défaut sur le répertoire Téléchargements.',\n  'transfer-settings': 'Transmission',\n  'transfer-speed-upload': 'Limite de téléversement',\n  'transfer-speed-download': 'Limite de téléchargement',\n  'transfer-speed-unlimited': 'Illimité',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Enregistrer le lien de l\\'aimant en tant que fichier torrent',\n  'bt-auto-download-content': 'Télécharger automatiquement l\\'aimant et le contenu du torrent',\n  'bt-force-encryption': 'Forcer le chiffrement de BT',\n  'keep-seeding': 'Continuez à semer jusqu\\'à ce que vous l\\'arrêtiez manuellement',\n  'seed-ratio': 'Ratio de semences',\n  'seed-time': 'Temps de semence',\n  'seed-time-unit': 'minutes',\n  'task-manage': 'Tâches',\n  'max-concurrent-downloads': 'Nombre de tâches active au maximum',\n  'max-connection-per-server': 'Nombre maximum de connexions par serveurs',\n  'new-task-show-downloading': 'Montrer automatiquement les téléchargements après l\\'ajout d\\'une tâche',\n  'no-confirm-before-delete-task': 'Aucune confirmation n\\'est requise avant de supprimer la tâche',\n  'continue': 'Continuer',\n  'task-completed-notify': 'Notifier à la fin d\\'un téléchargement',\n  'auto-purge-record': 'Purger l\\'historique de téléchargement lorsque vous quittez l\\'application',\n  'ui': 'UI',\n  'appearance': 'Mode d\\'apparence',\n  'theme-auto': 'Automatique',\n  'theme-light': 'Clair',\n  'theme-dark': 'Sombre',\n  'auto-hide-window': 'Masquer automatiquement les fenêtres',\n  'run-mode': 'Courir comme',\n  'run-mode-standard': 'Application standard',\n  'run-mode-tray': 'Application de la zone de notification',\n  'run-mode-hide-tray': 'Masquer l\\'application de la barre d\\'état système',\n  'tray-speedometer': 'La barre de menus affiche la vitesse en temps réel',\n  'show-progress-bar': 'Afficher la barre de progression du téléchargement',\n  'language': 'Langues',\n  'change-language': 'Changer la langue',\n  'hide-app-menu': 'Cacher le menu de l\\'application (Windows & Linux uniquement)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Activer le Proxy',\n  'proxy-bypass-input-tips': 'Contourner les paramètres de proxy pour ces hôtes et domaines, un par ligne',\n  'proxy-scope-download': 'Télécharger',\n  'proxy-scope-update-app': 'Mettre à jour l\\'application',\n  'proxy-scope-update-trackers': 'Mettre à jour les trackers',\n  'proxy-tips': 'Afficher le manuel du proxy',\n  'bt-tracker': 'Serveurs Tracker',\n  'bt-tracker-input-tips': 'Serveur de suivi, un par ligne',\n  'bt-tracker-tips': 'Recommander:',\n  'sync-tracker-tips': 'Sync',\n  'auto-sync-tracker': 'Mettre à jour automatiquement la liste des trackers chaque jour',\n  'port': 'Ports d\\'écoute',\n  'bt-port': 'Ports d\\'écoute BT',\n  'dht-port': 'Ports d\\'écoute DHT',\n  'security': 'Sécurité',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Port d\\'écoute RPC',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Voir le manuel secret RPC',\n  'developer': 'Développeur',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Chemin intégré de aria2.conf',\n  'app-log-path': 'Chemin des logs',\n  'download-session-path': 'Chemin de la session de téléchargement',\n  'factory-reset': 'Réinitialisation',\n  'factory-reset-confirm': 'Êtes vous sûr de vouloir réinitialiser les paramètres',\n  'lab-warning': '⚠️ Activer les fonctionalités labo peut causer des crash ou la perte de données !',\n  'download-protocol': 'Protocole',\n  'protocols-default-client': 'Définir comme client par défaut pour les protocoles suivants',\n  'protocols-magnet': 'Aimant [ magnet:// ]',\n  'protocols-thunder': 'Tonnerre [ thunder:// ]',\n  'browser-extensions': 'Extensions',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Fourni par la communauté, ',\n  'baidu-exporter-help': 'Cliquez ici pour voir l\\'utilisation',\n  'auto-update': 'Mettre à jour',\n  'auto-check-update': 'Mise à jour automatique',\n  'last-check-update-time': 'dernier contrôle la mise à jour du temps',\n  'not-saved': 'Préférences non enregistrées',\n  'not-saved-confirm': 'Les préférences modifiées seront perdues, êtes-vous sûr de partir ?'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/subnav.js",
    "content": "export default {\n  'task-list': 'Tâches',\n  'preferences': 'Préférences'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/task.js",
    "content": "export default {\n  'active': 'Actives',\n  'waiting': 'En attente',\n  'stopped': 'Stopées',\n  'new-task': 'Nouvelle Tâche',\n  'new-bt-task': 'Nouvelle Tâche BT',\n  'open-file': 'Ouvrir un fichier torrent...',\n  'uri-task': 'Lien',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Un lien par ligne (supporte les magnets)',\n  'thunder-link-tips': 'Astuce: Les liens Thunder ne doivent pas être téléchargés après décodage',\n  'new-task-uris-required': 'Veuillez entrer au moins une URL de ressource valide',\n  'new-task-torrent-required': 'Veuillez sélectionner un fichier torrent',\n  'file-name': 'Nom de fichier',\n  'file-extension': 'Ext',\n  'file-size': 'Taille',\n  'file-completed-size': 'Téléchargé',\n  'selected-files-sum': 'Sélectionné: {{selectedFilesCount}} fichiers, total {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Veuillez sélectionner au moins un fichier',\n  'task-gid': 'GID',\n  'task-name': 'Nom de la tâche',\n  'task-out': 'Renommer',\n  'task-out-tips': 'Optionel',\n  'task-split': 'Découpages',\n  'task-dir': 'Répertoire',\n  'pause-task': 'Mettre en Pause',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorisation',\n  'task-referer': 'Référent',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Erreur',\n  'task-piece': 'Pièce',\n  'task-piece-length': 'Taille de la pièce',\n  'task-num-pieces': 'Pièces',\n  'task-bittorrent-info': 'Informations sur le torrent',\n  'task-info-hash': 'Hacher',\n  'task-bittorrent-creation-date': 'Date de création',\n  'task-bittorrent-comment': 'Commenter',\n  'task-progress-info': 'Le progrès',\n  'task-status': 'Statut',\n  'task-num-seeders': 'Semoirs',\n  'task-connections': 'Connexions',\n  'task-file-size': 'Taille',\n  'task-download-speed': 'Vitesse de téléchargement',\n  'task-upload-speed': 'Vitesse de téléchargement',\n  'task-download-length': 'Téléchargé',\n  'task-upload-length': 'Téléchargé',\n  'task-ratio': 'Rapport',\n  'task-peer-host': 'Hôte',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Client',\n  'navigate-to-downloading': 'Aller au Téléchargement',\n  'show-advanced-options': 'Options Avancées',\n  'copyright-warning': 'Avertissement Copyright',\n  'copyright-warning-message': 'Le fichier que vous voulez télécharger peut être une vidéo ou de l\\'audio soumie aux copyright, verifiez que vous possédez bien sa license.',\n  'copyright-yes': 'Oui, je l\\'ai',\n  'copyright-no': 'Non',\n  'copyright-error-message': 'Ajout de la tâche annulé, pas de copyright',\n  'pause-task-success': 'Mise en pause de \"{{taskName}}\" avec succés',\n  'pause-task-fail': 'Mise en pause de \"{{taskName}}\" échouée',\n  'resume-task': 'Reprendre',\n  'resume-task-success': 'Reprise de \"{{taskName}}\" avec succés',\n  'resume-task-fail': 'Reprise de \"{{taskName}}\" échouée',\n  'delete-task': 'Supprimer',\n  'delete-selected-tasks': 'Supprimer la séléction',\n  'delete-task-confirm': 'Étes vous sûr de vouloir supprimer \"{{taskName}}\" ?',\n  'batch-delete-task-confirm': 'Voulez-vous vraiment supprimer {{count}} tâches de téléchargement par lot?',\n  'delete-task-label': 'Supprimer avec les fichiers',\n  'delete-task-success': 'Suppression de \"{{taskName}}\" avec succés',\n  'delete-task-fail': 'Suppression de \"{{taskName}}\" échouée',\n  'remove-task-file-fail': 'Impossible de supprimer la tâche, s\\'il vous plait supprimez la manuellement',\n  'remove-task-config-file-fail': 'Impossible de supprimer le fichier de configuration de la tâche, s\\'il vous plait supprimez le manuellement',\n  'move-task-up': 'Déplacer vers le haut',\n  'move-task-down': 'Déplacer vers le bas',\n  'pause-all-task': 'Tout mettre en pause',\n  'pause-all-task-success': 'Mise en pause réussie',\n  'pause-all-task-fail': 'Mise en pause échouée',\n  'resume-all-task': 'Tout reprendre',\n  'resume-all-task-success': 'Reprise réussie',\n  'resume-all-task-fail': 'Reprise échouée',\n  'select-all-task': 'Sélectionnez toutes les tâches',\n  'clear-recent-tasks': 'Effacer les tâches récentes',\n  'purge-record': 'Purger les tâches récentes',\n  'purge-record-success': 'Purge des tâches récentes réussie',\n  'purge-record-fail': 'Purge des tâches récentes échouée',\n  'batch-delete-task-success': 'Supprimez avec succès les tâches du lot',\n  'batch-delete-task-fail': 'Impossible de supprimer les tâches du lot',\n  'refresh-list': 'Rafraichir la liste des tâches',\n  'no-task': 'Aucun téléchargement en cours',\n  'copy-link': 'Copier le lien',\n  'copy-link-success': 'Lien copié',\n  'remove-record': 'Effacer l\\'enregistrement des tâches',\n  'remove-record-confirm': 'Étes vous sûr de vouloir effacer \"{{taskName}}\" ?',\n  'remove-record-label': 'Supprimer avec les fichiers',\n  'remove-record-success': '\"{{taskName}}\" éffacée',\n  'remove-record-fail': '\"{{taskName}}\" n\\'a pas été éffacée',\n  'show-in-folder': 'Montrer la tâche dans le dossier',\n  'file-not-exist': 'Le fichier n\\'existe pas ou a été supprimé',\n  'file-path-error': 'Erreur de lien sur le fichier',\n  'opening-task-message': 'Ouverture de \"{{taskName}}\" ...',\n  'get-task-name': 'Récupération du nom de la tâche...',\n  'remaining-prefix': 'Restant',\n  'select-torrent': 'Glissez-déposez un torrent ici, ou cliquez pour en choisir un',\n  'task-info-dialog-title': '{{title}} Details',\n  'download-start-message': 'Téléchargment de {{taskName}}',\n  'download-pause-message': 'Mise en pause de {{taskName}}',\n  'download-stop-message': '{{taskName}} téléchargment arrété',\n  'download-error-message': '{{taskName}} une erreur c\\'est produite',\n  'download-complete-message': '{{taskName}} téléchargement terminé',\n  'download-complete-notify': 'Téléchargement Terminé',\n  'bt-download-complete-message': '{{taskName}} téléchargement terminé, ensemencement...',\n  'bt-download-complete-notify': 'BT Télécharger Terminé, Ensemencement...',\n  'bt-download-complete-tips': 'Astuces: Vous pouvez arrêter la tâche pour mettre fin à l\\'ensemencement',\n  'bt-stopping-seeding-tip': 'Arrêt de l\\'ensemencement, la déconnexion prendra un certain temps, veuillez patienter ...',\n  'download-fail-message': '{{taskName}} téléchargement échoué',\n  'download-fail-notify': 'Téléchargement Échoué'\n}\n"
  },
  {
    "path": "src/shared/locales/fr/window.js",
    "content": "export default {\n  'reload': 'Recharger',\n  'close': 'Fermer',\n  'minimize': 'Réduire',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Passer en mode Plein écran',\n  'front': 'Tout mettre au premier plan'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/about.js",
    "content": "export default {\n  'engine-version': 'Motor verziója',\n  'license': 'Licensz',\n  'about': 'Névjegy',\n  'release': 'Kiadások',\n  'support': 'Segítség kérés'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/app.js",
    "content": "export default {\n  'task-list': 'Feladatok',\n  'add-task': 'Feladat hozzáadasa',\n  'about': 'Motrix névjegye',\n  'preferences': 'Beálittasok...',\n  'check-for-updates': 'Frissitések keresése...',\n  'check-updates-now': 'Elenörzés most',\n  'checking-for-updates': 'Frissitések keresése...',\n  'check-for-updates-title': 'Frissitések keresése',\n  'update-available-message': 'Egy újabb Motrix verzió elérhető. Telepitsem most?',\n  'update-not-available-message': 'Nincs elérhető frissités!',\n  'update-downloaded-message': 'Telepitésre kész...',\n  'update-error-message': 'Hiba történt frissités közben',\n  'engine-damaged-message': 'A motor meghibásodot, kérjük telepitse újra a Motrix-ot : (',\n  'engine-missing-message': 'A motor hiányzik, kérjük telepitse újra a Motrix-ot : (',\n  'system-error-title': 'Rendszer hiba',\n  'system-error-message': 'Motrix inditása sikertelen: {{message}}',\n  'hide': 'Motrix elrejtése',\n  'hide-others': 'Egyebek elrejtése',\n  'unhide': 'Minden megjelenitése',\n  'show': 'Motrix mutatása',\n  'quit': 'Kilépés',\n  'under-development-message': 'Sájnaljuk, de ez a funkció feljesztés allat...',\n  'yes': 'Igen',\n  'no': 'Nem',\n  'save': 'Mentés',\n  'reset': 'Eldobni',\n  'cancel': 'Mégse',\n  'submit': 'Beküldés',\n  'gt1d': '> 1 nap',\n  'hour': 'ó',\n  'minute': 'p',\n  'second': 'm'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/edit.js",
    "content": "export default {\n  'undo': 'Visszavonás',\n  'redo': 'Mégis',\n  'cut': 'Kivágas',\n  'copy': 'Másolas',\n  'paste': 'Beilesztés',\n  'delete': 'Törlés',\n  'select-all': 'Minden kijelölés'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/help.js",
    "content": "export default {\n  'official-website': 'Motrix Weboldala',\n  'manual': 'Kézikönyv',\n  'release-notes': 'Valtózások...',\n  'report-problem': 'Hiba jelentés',\n  'toggle-dev-tools': 'Fejlesztöi eszközök'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/hu/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Fájl',\n  'task': 'Feladat',\n  'edit': 'Szerkesztés',\n  'window': 'Ablak',\n  'help': 'Segitség'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/preferences.js",
    "content": "export default {\n  'basic': 'Alap',\n  'advanced': 'Haladó',\n  'lab': 'Lab',\n  'save': 'Mentés és alkalmazás',\n  'save-success-message': 'Mentés sikeres volt',\n  'save-fail-message': 'Mentés sikertelen volt',\n  'discard': 'Elvetés',\n  'startup': 'Inditás',\n  'open-at-login': 'Megnyitás bejelenkezéskor',\n  'keep-window-state': 'Ablak méretének megtartása kilépéskor',\n  'auto-resume-all': 'Automatikusan folytasa a félbehagyot feladatokat',\n  'default-dir': 'Alap mappa',\n  'mas-default-dir-tips': 'A App Store korlatozása miatt ájanlott a ~/Downloads mappaba tenni a letöltéseket',\n  'transfer-settings': 'Transmission',\n  'transfer-speed-upload': 'Feltöltés limit',\n  'transfer-speed-download': 'Letöltés limit',\n  'transfer-speed-unlimited': 'Végtelen',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Mágneses link mentése torrent fájlként',\n  'bt-auto-download-content': 'A mágnes és a torrent tartalma automatikus letöltése',\n  'bt-force-encryption': 'BT erőszakos titkosítása',\n  'keep-seeding': 'Addig folytassa a vetéset, amíg manuálisan le nem állítja',\n  'seed-ratio': 'Magarány',\n  'seed-time': 'Seed Time',\n  'seed-time-unit': 'percek',\n  'task-manage': 'Feladatok kezelése',\n  'max-concurrent-downloads': 'Maximum feladatok',\n  'max-connection-per-server': 'Maximum csatlakozás szerverenként',\n  'new-task-show-downloading': 'Automatikusan mutasa meg a letöltéseket befejezésnél',\n  'no-confirm-before-delete-task': 'Ne legyen megerösités a törlésnél',\n  'continue': 'Folytatás',\n  'task-completed-notify': 'Értesités a feladat befejezésnél',\n  'auto-purge-record': 'Automatikusan tisztitsa a feladatokat kilépéskor',\n  'ui': 'UI',\n  'appearance': 'Személyre szabás',\n  'theme-auto': 'Automatikus',\n  'theme-light': 'Villágos',\n  'theme-dark': 'Sötét',\n  'auto-hide-window': 'Automatikusan rejtse el a ablakot',\n  'run-mode': 'Inditás néven...',\n  'run-mode-standard': 'Alap alkalmazás',\n  'run-mode-tray': 'Tálcás alkalmazás',\n  'run-mode-hide-tray': 'Tálca alkalmazás elrejtése',\n  'tray-speedometer': 'Menu csik mutasa a letöltés és feltöltés sebbeséget',\n  'show-progress-bar': 'Letöltési előrehaladási sáv megjelenítése',\n  'language': 'Nyelv',\n  'change-language': 'Nyelv váltas',\n  'hide-app-menu': 'Alkalmazás csik elrejtése (csak Windows és Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Proxy engedélyezése',\n  'proxy-bypass-input-tips': 'Proxy beálitassok elvetése ezek url és domain-nél (/sor)',\n  'proxy-scope-download': 'Letöltés',\n  'proxy-scope-update-app': 'Alkalmazás frissítése',\n  'proxy-scope-update-trackers': 'Nyomkövetők frissítése',\n  'proxy-tips': 'Proxy kéziköny megnyitasa',\n  'bt-tracker': 'Lekövetö szerverek',\n  'bt-tracker-input-tips': 'Lekövetö szerverek (/sor)',\n  'bt-tracker-tips': 'Ájanlass: ',\n  'sync-tracker-tips': 'Szinkronizalás',\n  'auto-sync-tracker': 'Lekövetö szerverek listaja frissitése naponta',\n  'port': 'Listen Ports',\n  'bt-port': 'BT Listen Port',\n  'dht-port': 'DHT Listen Port',\n  'security': 'Biztonság',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC-hallgató-port',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'RPC Secret kézikönyv megnyitasa',\n  'developer': 'feljesztö',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'User-Agent-t',\n  'aria2-conf-path': 'Beépített aria2.conf útvonal',\n  'app-log-path': 'Alkalmazásnapló helye',\n  'download-session-path': 'Letöltés folyamat helye',\n  'factory-reset': 'Gyári viszaálitas',\n  'factory-reset-confirm': 'Biztos vézel gyári viszaálitas?',\n  'lab-warning': '⚠️ Ha engedélyezed a feljesztésben lévő funkciokat akkor lehetséges a adat sérülés',\n  'download-protocol': 'Protokol',\n  'protocols-default-client': 'Alap kliens Protokol',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Bővítmény',\n  'baidu-exporter': 'BaiduExportálo',\n  'browser-extensions-tips': 'Közzönség adat, ',\n  'baidu-exporter-help': 'Kattincs ide a hasznalati naplohoz',\n  'auto-update': 'Automatikus frissités',\n  'auto-check-update': 'Automatikus keresen frissitéseket',\n  'last-check-update-time': 'Legutolsó frissités:',\n  'not-saved': 'A beállítások nincsenek mentve',\n  'not-saved-confirm': 'A megváltozott beállítások elvesznek. Biztosan kilép?'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/subnav.js",
    "content": "export default {\n  'task-list': 'Feladat',\n  'preferences': 'Beallitás'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/task.js",
    "content": "export default {\n  'active': 'Letöltés',\n  'waiting': 'Varakozás',\n  'stopped': 'Lealitva',\n  'new-task': 'Feladat hozzáadasa',\n  'new-bt-task': 'Új BT Feladat hozzáadasa',\n  'open-file': 'Torrent fájl megnyitása...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Egy feladat (/sor) (magnet tamogátas)',\n  'thunder-link-tips': 'Tip: Thunder linkek nem letölthetöek decoding után',\n  'new-task-uris-required': 'Kérjük addjon meg egy valós eröforrás linket',\n  'new-task-torrent-required': 'Kérjük válasszon egy valós torrent fájlt',\n  'file-name': 'Név',\n  'file-extension': 'Kiterjesztés',\n  'file-size': 'Méret',\n  'file-completed-size': 'Letöltött',\n  'selected-files-sum': 'Kijelölve: {{selectedFilesCount}} fájl, teljes méret {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Válasszon legalább egy fájlt',\n  'task-gid': 'GID',\n  'task-name': 'Feladat név',\n  'task-out': 'Átnevezés',\n  'task-out-tips': 'Opcionális',\n  'task-split': 'Elosztás',\n  'task-dir': 'Mentés ide',\n  'pause-task': 'Feladat szüneteltetése',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Felhatalmazás',\n  'task-referer': 'Átíranyitó',\n  'task-cookie': 'Süti',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Hiba',\n  'task-piece': 'Darab',\n  'task-piece-length': 'Darabméret',\n  'task-num-pieces': 'Darabok',\n  'task-bittorrent-info': 'Torrent információ',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Létrehozás dátuma',\n  'task-bittorrent-comment': 'Megjegyzés',\n  'task-progress-info': 'Előrehalad',\n  'task-status': 'Állapot',\n  'task-num-seeders': 'Vetőgépek',\n  'task-connections': 'Kapcsolatok',\n  'task-file-size': 'Méret',\n  'task-download-speed': 'Letöltési sebesség',\n  'task-upload-speed': 'Feltöltési sebesség',\n  'task-download-length': 'Letöltött',\n  'task-upload-length': 'Feltöltve',\n  'task-ratio': 'Hányados',\n  'task-peer-host': 'Házigazda',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Ügyfél',\n  'navigate-to-downloading': 'Navigálas a letöltéshez',\n  'show-advanced-options': 'Haladó beallitás',\n  'copyright-warning': 'Szerzöi jogi figyelmeztetés',\n  'copyright-warning-message': 'A fájl Szerzöi jogi akkor gyözödjön meg arol hogy van joga hozzá.',\n  'copyright-yes': 'Igen, van jogom hozzá',\n  'copyright-no': 'Nem, nincs jogom hozzá',\n  'copyright-error-message': 'Nem sikerült hozzá addni a feladatot szerzöi jog miatt',\n  'pause-task-success': 'Sikeresen szüneteltettve \"{{taskName}}\"',\n  'pause-task-fail': 'Sikeresen volt a szünetelés \"{{taskName}}\"',\n  'resume-task': 'Feladat folytatása',\n  'resume-task-success': 'Feladat folytatása sikeres \"{{taskName}}\"',\n  'resume-task-fail': 'Feladat folytatása sikertelen \"{{taskName}}\"',\n  'delete-task': 'Feladat törlése',\n  'delete-selected-tasks': 'Kijelölt feladatok törlése',\n  'delete-task-confirm': 'Biztosan törlod a \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Biztosan törölsz {{count}} feladatot?',\n  'delete-task-label': 'Törlés fájlokal',\n  'delete-task-success': 'Sikeresen törölve \"{{taskName}}\"',\n  'delete-task-fail': 'Sikertelen volt a törlés \"{{taskName}}\"',\n  'remove-task-file-fail': 'Sikertelen volt a fájlok törlés, kérjük törölje manualisan',\n  'remove-task-config-file-fail': 'Sikertelen volt a feladat konfiguráció törlése, kérjük törölje manualisan',\n  'move-task-up': 'Feladat mozgatása fel',\n  'move-task-down': 'Feladat mozgatása le',\n  'pause-all-task': 'Minden feladat szünetelése',\n  'pause-all-task-success': 'Minden feladat szünetelése sikeres volt',\n  'pause-all-task-fail': 'Minden feladat szünetelése sikertelen volt',\n  'resume-all-task': 'Minden feladat folytatása',\n  'resume-all-task-success': 'Minden feladat folytatása sikeres volt',\n  'resume-all-task-fail': 'Minden feladat folytatása sikertelen volt',\n  'select-all-task': 'Összes feladat Kijelölése',\n  'clear-recent-tasks': 'Elözö feladat törlése',\n  'purge-record': 'Feladatok tisztitása',\n  'purge-record-success': 'Feladatok tisztitása sikeres volt',\n  'purge-record-fail': 'Feladatok tisztitása sikertelen volt',\n  'batch-delete-task-success': 'Feladat törlése sikeres volt',\n  'batch-delete-task-fail': 'Feladat törlése sikertelen volt',\n  'refresh-list': 'Feladat lista frissétése',\n  'no-task': 'Nincs aktív feladat',\n  'copy-link': 'Link másolasa',\n  'copy-link-success': 'Link másolasa sikeres volt',\n  'remove-record': 'Feladat törlése',\n  'remove-record-confirm': 'Biztos törlöd a \"{{taskName}}\"-et?',\n  'remove-record-label': 'Fájl törlése',\n  'remove-record-success': 'Feladat törlése sikeres volt \"{{taskName}}\"',\n  'remove-record-fail': 'Feladat törlése sikertelen volt \"{{taskName}}\"',\n  'show-in-folder': 'Feladat mappa megnyitása',\n  'file-not-exist': 'A fájl törölve vagy áthelyezve',\n  'file-path-error': 'Fájl hely hiba',\n  'opening-task-message': '\"{{taskName}}\"Megynitasa...',\n  'get-task-name': 'Feladat név megnyitása...',\n  'remaining-prefix': 'Maradt',\n  'select-torrent': 'Húzz ide torrent fájl vagy talloz',\n  'task-info-dialog-title': '{{title}} részletek',\n  'download-start-message': 'Letöltés inditása {{taskName}}',\n  'download-pause-message': 'Letöltés szüneteltettve {{taskName}}',\n  'download-stop-message': 'Letöltés megallitva {{taskName}}',\n  'download-error-message': 'Hiba történk letöltés közben {{taskName}}',\n  'download-complete-message': 'Letöltés befejezve {{taskName}}',\n  'download-complete-notify': 'Letöltés befejezve',\n  'bt-download-complete-message': 'Letöltés befejezve {{taskName}}, tovvábadás',\n  'bt-download-complete-notify': 'BT Letöltés befejezve, tovvábadás...',\n  'bt-download-complete-tips': 'Tips: Be tudod fejezni a tovvábadás',\n  'bt-stopping-seeding-tip': 'tovvábadás befejezése, Ez eltarthat par percig...',\n  'download-fail-message': 'Letöltés sikertelen volt {{taskName}}',\n  'download-fail-notify': 'Letöltés sikertelen volt'\n}\n"
  },
  {
    "path": "src/shared/locales/hu/window.js",
    "content": "export default {\n  'reload': 'Újratöltés',\n  'close': 'Bezáras',\n  'minimize': 'Kis méret',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Belépés teljes képernyöbe',\n  'front': 'Minden elöre'\n}\n"
  },
  {
    "path": "src/shared/locales/id/about.js",
    "content": "export default {\n  'engine-version': 'Versi Mesin',\n  'license': 'Lisensi',\n  'about': 'Tentang',\n  'release': 'Rilis',\n  'support': 'Bantuan'\n}\n"
  },
  {
    "path": "src/shared/locales/id/app.js",
    "content": "export default {\n  'task-list': 'Tugas',\n  'add-task': 'Tambah Tugas',\n  'about': 'Tentang Motrix',\n  'preferences': 'Preferensi...',\n  'check-for-updates': 'Periksa Pembaruan...',\n  'check-updates-now': 'Periksa Sekarang',\n  'checking-for-updates': 'Memeriksa pembaruan...',\n  'check-for-updates-title': 'Periksa Pembaruan',\n  'update-available-message': 'Versi Motrix terbaru telah tersedia, perbarui sekarang?',\n  'update-not-available-message': 'Aplikasi dalam kondisi ter-update!',\n  'update-downloaded-message': 'siap meng-install...',\n  'update-error-message': 'Update Gagal',\n  'engine-damaged-message': 'Mesin rusak, silahkan install ulang : (',\n  'engine-missing-message': 'Mesin hilang, silahkan install ulang : (',\n  'system-error-title': 'System Error',\n  'system-error-message': 'Gagal Menjalankan Aplikasi: {{message}}',\n  'hide': 'Sembungikan Motrix',\n  'hide-others': 'Sembunyikan yang lain',\n  'unhide': 'Tunjukan Semua',\n  'show': 'Tunjukan Motrix',\n  'quit': 'Keluarkan Motrix',\n  'under-development-message': 'Maaf, fitur ini dalam tahap development...',\n  'yes': 'Ya',\n  'no': 'Tidak',\n  'save': 'Menyimpan',\n  'reset': 'Membuang',\n  'cancel': 'Batal',\n  'submit': 'Kirim',\n  'gt1d': '> 1 hari',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/id/edit.js",
    "content": "export default {\n  'undo': 'Urungkan',\n  'redo': 'Ulangi',\n  'cut': 'Potong',\n  'copy': 'Salin',\n  'paste': 'Tempel',\n  'delete': 'Hapus',\n  'select-all': 'Pilih Semua'\n}\n"
  },
  {
    "path": "src/shared/locales/id/help.js",
    "content": "export default {\n  'official-website': 'Motrix Website',\n  'manual': 'Panduan',\n  'release-notes': 'Catatan Rilis...',\n  'report-problem': 'Laporkan Masalah',\n  'toggle-dev-tools': 'Alihkan Alat Pengembang'\n}\n"
  },
  {
    "path": "src/shared/locales/id/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/id/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Berlas',\n  'task': 'Tugas',\n  'edit': 'Edit',\n  'window': 'Window',\n  'help': 'Bantuan'\n}\n"
  },
  {
    "path": "src/shared/locales/id/preferences.js",
    "content": "export default {\n  'basic': 'Pengaturan Dasar',\n  'advanced': 'Pengaturan Lanjut',\n  'lab': 'Lab',\n  'save': 'Simpan & Terapkan',\n  'save-success-message': 'Berhasil menyimpan pengaturan',\n  'save-fail-message': 'Gagal menyimpan pengaturan',\n  'discard': 'Batal',\n  'startup': 'Memulai',\n  'open-at-login': 'Buka saat login',\n  'keep-window-state': 'Pertahankan ukuran dan posisi jendela aplikasi saat keluar',\n  'auto-resume-all': 'Otomatis lanjutkan semua tugas yang belum selesai',\n  'default-dir': 'Lokasi Bawaan',\n  'mas-default-dir-tips': 'Karena pembatasan dari App Store, direktori unduhan default direkomendasikan untuk disetel ke ~/Downloads',\n  'transfer-settings': 'Transfer Setting',\n  'transfer-speed-upload': 'Limit Unggah',\n  'transfer-speed-download': 'Limit Unduh',\n  'transfer-speed-unlimited': 'Tak Terbatas',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Simpan tautan magnet sebagai file torrent',\n  'bt-auto-download-content': 'Secara otomatis mengunduh magnet dan konten torrent',\n  'bt-force-encryption': 'Memaksa enkripsi paksa BT',\n  'keep-seeding': 'Terus lakukan penyemaian sampai menghentikannya secara manual',\n  'seed-ratio': 'Rasio Benih',\n  'seed-time': 'Waktu Benih',\n  'seed-time-unit': 'menit',\n  'task-manage': 'Pengelola Tugas',\n  'max-concurrent-downloads': 'Maksimal tugas aktif',\n  'max-connection-per-server': 'Maksimal koneksi per server',\n  'new-task-show-downloading': 'Tampilkan pengunduhan secara otomatis setelah menambahkan tugas',\n  'no-confirm-before-delete-task': 'Konfirmasi tidak diperlukan sebelum menghapus tugas',\n  'continue': 'Lanjutkan',\n  'task-completed-notify': 'Pemberitahuan setelah pengunduhan selesai',\n  'auto-purge-record': 'Otomatis bersihkan catatan unduhan saat keluar dari aplikasi',\n  'ui': 'UI',\n  'appearance': 'Penampilan',\n  'theme-auto': 'Auto',\n  'theme-light': 'Terang',\n  'theme-dark': 'Gelap',\n  'auto-hide-window': 'Sembunyikan Otomatis Jendela',\n  'run-mode': 'Jalankan Sebagai',\n  'run-mode-standard': 'Aplikasi Standar',\n  'run-mode-tray': 'Aplikasi Tray',\n  'run-mode-hide-tray': 'Sembunyikan Aplikasi Tray',\n  'tray-speedometer': 'Baki menu bar menunjukkan kecepatan waktu-nyata',\n  'show-progress-bar': 'Tampilkan bilah kemajuan unduhan',\n  'language': 'Bahasa',\n  'change-language': 'Ubah Bahasa',\n  'hide-app-menu': 'Sembunyikan Menu Aplikasi (hanya: Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Aktifkan Proxy',\n  'proxy-bypass-input-tips': 'Abaikan pengaturan proxy untuk Host dan Domain ini, satu per baris',\n  'proxy-scope-download': 'Unduh',\n  'proxy-scope-update-app': 'Perbarui Aplikasi',\n  'proxy-scope-update-trackers': 'Perbarui Pelacak',\n  'proxy-tips': 'Lihat Manual Proxy',\n  'bt-tracker': 'Server Pelacak',\n  'bt-tracker-input-tips': 'Server pelacak, satu per baris',\n  'bt-tracker-tips': 'Direkomendasikan: ',\n  'sync-tracker-tips': 'Sinkronkan',\n  'auto-sync-tracker': 'Perbarui daftar pelacak setiap hari secara otomatis',\n  'port': 'Dengarkan Ports',\n  'bt-port': 'Dengarkan Port BT',\n  'dht-port': 'Dengarkan Port DHT',\n  'security': 'Keamanan',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Port Dengar RPC',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Lihat Petunjuk RPC Secret',\n  'developer': 'Developer',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Jalur aria2.conf Bawaan',\n  'app-log-path': 'Lokasi Log Aplikasi',\n  'download-session-path': 'Lokasi Session Unduhan',\n  'factory-reset': 'Reset Pabrik',\n  'factory-reset-confirm': 'Anda yakin ingin kembali ke pengaturan pabrik?',\n  'lab-warning': 'Mengaktifkan fitur lab dapat menyebabkan aplikasi tidak berjalan semestinya atau kehilangan data, risiko ditanggung Anda sendiri!',\n  'download-protocol': 'Protocols',\n  'protocols-default-client': 'Tetapkan sebagai klien untuk Protocol berikut',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Ekstensi',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Disediakan oleh komunitas, ',\n  'baidu-exporter-help': 'Klik di sini untuk penggunaan',\n  'auto-update': 'Pembaruan Otomatis',\n  'auto-check-update': 'Secara otomatis memeriksa pembaruan',\n  'last-check-update-time': 'Terakhir Kali Memeriksa Pembaruan',\n  'not-saved': 'Preferensi tidak disimpan',\n  'not-saved-confirm': 'Preferensi yang dimodifikasi akan hilang, apakah Anda yakin untuk keluar?'\n}\n"
  },
  {
    "path": "src/shared/locales/id/subnav.js",
    "content": "export default {\n  'task-list': 'Daftar Tugas',\n  'preferences': 'Pengaturan'\n}\n"
  },
  {
    "path": "src/shared/locales/id/task.js",
    "content": "export default {\n  'active': 'Mengunduh',\n  'waiting': 'Mengunggu',\n  'stopped': 'Terhenti',\n  'new-task': 'Tugas Baru',\n  'new-bt-task': 'Tugas BT baru',\n  'open-file': 'Buka Berkas Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Satu tugas per baris (mendukung magnet)',\n  'thunder-link-tips': 'Tip: Thunder tautan mungkin tidak dapat diunduh setelah decoding',\n  'new-task-uris-required': 'Silakan masukkan setidaknya satu url yang valid',\n  'new-task-torrent-required': 'Silahkan pilih berkas torrent',\n  'file-name': 'Nama',\n  'file-extension': 'Perpanjangan',\n  'file-size': 'Ukuran',\n  'file-completed-size': 'Ukuran domplet',\n  'selected-files-sum': 'Terpilih: {{selectedFilesCount}} berkas, total ukuran {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Pilih setidaknya satu file',\n  'task-gid': 'GID',\n  'task-name': 'Nama Tugas',\n  'task-out': 'Ubah Nama',\n  'task-out-tips': 'Opsional',\n  'task-split': 'Pecahan',\n  'task-dir': 'Simpan Ke',\n  'pause-task': 'Tunda Tugas',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Otorisasi',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Kesalahan',\n  'task-piece': 'Bagian',\n  'task-piece-length': 'Ukuran Potongan',\n  'task-num-pieces': 'Potongan',\n  'task-bittorrent-info': 'Info Torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Tanggal Pembuatan',\n  'task-bittorrent-comment': 'Komentar',\n  'task-progress-info': 'Kemajuan',\n  'task-status': 'Status',\n  'task-num-seeders': 'Seeders',\n  'task-connections': 'Koneksi',\n  'task-file-size': 'Ukuran',\n  'task-download-speed': 'Kecepatan Download',\n  'task-upload-speed': 'Kecepatan unggah',\n  'task-download-length': 'Telah diunduh',\n  'task-upload-length': 'Diupload',\n  'task-ratio': 'Perbandingan',\n  'task-peer-host': 'Tuan rumah',\n  'task-peer-ip': 'AKU P',\n  'task-peer-client': 'Klien',\n  'navigate-to-downloading': 'Beralih ke Unduhan',\n  'show-advanced-options': 'Setting Lanjutan',\n  'copyright-warning': 'Peringatan Hak Cipta',\n  'copyright-warning-message': 'File yang ingin Anda unduh mungkin berupa audio atau video yang dilindungi hak cipta, pastikan Anda memiliki izin untuk mengaksesnya.',\n  'copyright-yes': 'Ya, Saya punya izin',\n  'copyright-no': 'Tidak, Saya tidak punya izin',\n  'copyright-error-message': 'Gagal menambahkan tugas karena masalah hak cipta',\n  'pause-task-success': 'Tugas berhasil ditunda \"{{taskName}}\"',\n  'pause-task-fail': 'Gagal menunda tugas \"{{taskName}}\"',\n  'resume-task': 'Lanjutkan tugas',\n  'resume-task-success': 'Berhasil melanjutkan tugas \"{{taskName}}\"',\n  'resume-task-fail': 'Gagal melanjutkan tugas \"{{taskName}}\"',\n  'delete-task': 'Hapus tugas',\n  'delete-selected-tasks': 'Hapus tugas terpilih',\n  'delete-task-confirm': 'Anda yakin ingin menghapus tugas unduhan \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Anda yakin ingin menghapus {{count}} tugas unduhan (batch)?',\n  'delete-task-label': 'Hapus dengan File',\n  'delete-task-success': 'Tugas \"{{taskName}}\" berhasil dihapus',\n  'delete-task-fail': 'Tugas \"{{taskName}}\" gagal dihapus',\n  'remove-task-file-fail': 'Gagal menghapus berkas tugas, silahkan hapus secara manual',\n  'remove-task-config-file-fail': 'Gagal menghapus pengaturan berkas tugas, silahkan hapus secara manual',\n  'move-task-up': 'Pidahkan Tugas ke Atas',\n  'move-task-down': 'Pidahkan Tugas ke Bawah',\n  'pause-all-task': 'Tunda Semua Tugas',\n  'pause-all-task-success': 'Berhasil menunda semua tugas',\n  'pause-all-task-fail': 'Gagal menunda semua tugas',\n  'resume-all-task': 'Lanjutkan Semua Tugas',\n  'resume-all-task-success': 'Berhasil melanjutkan semua tugas',\n  'resume-all-task-fail': 'Gagal melanjutkan semua tugas',\n  'select-all-task': 'Pilih Semua Tugas',\n  'clear-recent-tasks': 'Bersihkan tugas terakhir',\n  'purge-record': 'Bersihkan Catatan Tugas',\n  'purge-record-success': 'Berhasil membersihkan catatan tugas',\n  'purge-record-fail': 'Gagal membersihkan catatan tugas',\n  'batch-delete-task-success': 'Berhasil menghapus tugas (batch)',\n  'batch-delete-task-fail': 'Gagal menghapus tugas (batch)',\n  'refresh-list': 'Muat Ulang Daftar',\n  'no-task': 'Tidak ada tugas',\n  'copy-link': 'Salin Link',\n  'copy-link-success': 'Berhasil menyalin link',\n  'remove-record': 'Hapus Data Tugas',\n  'remove-record-confirm': 'Anda yakin ingin menghapus data unduhan \"{{taskName}}\"?',\n  'remove-record-label': 'Hapus dengan Berkas',\n  'remove-record-success': 'Catatan tugas berhasil dihapus untuk \"{{taskName}}\"',\n  'remove-record-fail': 'Gagal menghapus catatan tugas untuk \"{{taskName}}\"',\n  'show-in-folder': 'Tampilkan Tugas Di Folder',\n  'file-not-exist': 'Berkas target tidak ada atau telah dihapus',\n  'file-path-error': 'Lokasi berkas error',\n  'opening-task-message': 'Membuka \"{{taskName}}\" ...',\n  'get-task-name': 'Mendapatkan nama tugas...',\n  'remaining-prefix': 'Tersisa',\n  'select-torrent': 'Seret berkas torrent ke sini, atau klik untuk memilih',\n  'task-info-dialog-title': '{{title}} Detail',\n  'download-start-message': 'Memulai mengunduh {{taskName}}',\n  'download-pause-message': 'Menunda mengunduh {{taskName}}',\n  'download-stop-message': 'Berhenti mengunduh {{taskName}}',\n  'download-error-message': 'Terjadi kesalahan saat mengunduh {{taskName}}',\n  'download-complete-message': 'Selesai mengunduh {{taskName}}',\n  'download-complete-notify': 'Unduh Selesai',\n  'bt-download-complete-message': 'Selesai mengunduh {{taskName}}, penyemaian',\n  'bt-download-complete-notify': 'BT Unduh Selesai, penyemaian...',\n  'bt-download-complete-tips': 'Tips: Anda dapat menghentikan tugas untuk mengakhiri penyemaian',\n  'bt-stopping-seeding-tip': 'Menghentikan penyemaian, perlu beberapa saat untuk memutuskan, harap tunggu...',\n  'download-fail-message': 'Gagal mengunduh {{taskName}}',\n  'download-fail-notify': 'Unduhan Gagal'\n}\n"
  },
  {
    "path": "src/shared/locales/id/window.js",
    "content": "export default {\n  'reload': 'Muat Ulang',\n  'close': 'Keluar',\n  'minimize': 'Perkecil',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Layar penuh',\n  'front': 'Bawa Semua ke Depan'\n}\n"
  },
  {
    "path": "src/shared/locales/index.js",
    "content": "/**\n * Welcome to translate to more versions in other languages.\n * Please read the translation guide before starting work.\n * https://github.com/agalwood/Motrix/blob/master/CONTRIBUTING.md#-translation-guide\n *\n * Please keep the locale key in alphabetical order.\n */\nexport const availableLanguages = [\n  {\n    value: 'ar',\n    label: 'عربي'\n  },\n  {\n    value: 'bg',\n    label: 'Българският език'\n  },\n  {\n    value: 'ca',\n    label: 'Català'\n  },\n  {\n    value: 'de',\n    label: 'Deutsch'\n  },\n  {\n    value: 'el',\n    label: 'Ελληνικά'\n  },\n  {\n    value: 'en-US',\n    label: 'English'\n  },\n  {\n    value: 'es',\n    label: 'Español'\n  },\n  {\n    value: 'fa',\n    label: 'فارسی'\n  },\n  {\n    value: 'fr',\n    label: 'Français'\n  },\n  {\n    value: 'hu',\n    label: 'Hungarian'\n  },\n  {\n    value: 'id',\n    label: 'Indonesia'\n  },\n  {\n    value: 'it',\n    label: 'Italiano'\n  },\n  {\n    value: 'ja',\n    label: '日本語'\n  },\n  {\n    value: 'ko',\n    label: '한국어'\n  },\n  {\n    value: 'nb',\n    label: 'Norsk Bokmål'\n  },\n  {\n    value: 'nl',\n    label: 'Nederlands'\n  },\n  {\n    value: 'pl',\n    label: 'Polski'\n  },\n  {\n    value: 'pt-BR',\n    label: 'Português (Brasil)'\n  },\n  {\n    value: 'ro',\n    label: 'Română'\n  },\n  {\n    value: 'ru',\n    label: 'Русский'\n  },\n  {\n    value: 'th',\n    label: 'แบบไทย'\n  },\n  {\n    value: 'tr',\n    label: 'Türkçe'\n  },\n  {\n    value: 'uk',\n    label: 'Українська'\n  },\n  {\n    value: 'vi',\n    label: 'Tiếng Việt'\n  },\n  {\n    value: 'zh-CN',\n    label: '简体中文'\n  },\n  {\n    value: 'zh-TW',\n    label: '繁體中文'\n  }\n]\n\nconst checkLngIsAvailable = (locale) => {\n  return availableLanguages.some(lng => lng.value === locale)\n}\n\n/**\n * getLanguage\n * @param { String } locale\n * https://www.electronjs.org/docs/api/app#appgetlocale\n *\n * Only these locales need to add a `startsWith` fallback\n * when there are with the same prefix\n *\n * ar, ar-XB\n * de, de-AT, de-CH, de-DE\n * en, en-AU, en-CA, en-GB, en-IN, en-NZ, en-US, en-XA, en-ZA\n * es, es-419, es-AR, es-CL, es-CO, es-CR, es-ES, es-HN, es-MX, es-PE, es-US, es-UY, es-VE\n * fr, fr-CA, fr-CH, fr-FR\n * it, it-CH, it-IT\n * pt, pt-BR, pt-PT\n * zh, zh-CN, zh-HK, zh-TW\n */\nexport const getLanguage = (locale = 'en-US') => {\n  if (checkLngIsAvailable(locale)) {\n    return locale\n  }\n\n  if (locale.startsWith('ar')) {\n    return 'ar'\n  }\n\n  if (locale.startsWith('de')) {\n    return 'de'\n  }\n\n  if (locale.startsWith('en')) {\n    return 'en-US'\n  }\n\n  if (locale.startsWith('es')) {\n    return 'es'\n  }\n\n  if (locale.startsWith('fr')) {\n    return 'fr'\n  }\n\n  if (locale.startsWith('it')) {\n    return 'it'\n  }\n\n  // If there is a pt-PT translation in the future,\n  // here will fallback to pt-PT.\n  if (locale.startsWith('pt')) {\n    return 'pt-BR'\n  }\n\n  if (locale === 'zh-HK') {\n    return 'zh-TW'\n  }\n\n  if (locale.startsWith('zh')) {\n    return 'zh-CN'\n  }\n}\n"
  },
  {
    "path": "src/shared/locales/it/about.js",
    "content": "export default {\n  'engine-version': 'Versione Engine',\n  'license': 'Licenza',\n  'about': 'Chi siamo',\n  'release': 'Rilasci',\n  'support': 'Supporto'\n}\n"
  },
  {
    "path": "src/shared/locales/it/app.js",
    "content": "export default {\n  'task-list': 'Attività',\n  'add-task': 'Aggiungi Attività',\n  'about': 'A proposito di Motrix',\n  'preferences': 'Preferenze...',\n  'check-for-updates': 'Verifica la disponibilità di aggiornamenti...',\n  'check-updates-now': 'Verifica ora',\n  'checking-for-updates': 'Sto verificando la disponibilità di aggiornamenti ...',\n  'check-for-updates-title': 'Verifica la disponibilità di aggiornamenti',\n  'update-available-message': 'Una nuova versione di Motrix è disponibile, aggiornare ora?',\n  'update-not-available-message': 'Applicazione già aggiornata!',\n  'update-downloaded-message': 'Pronto per l\\'installazione...',\n  'update-error-message': 'Errore nell\\'aggiornamento',\n  'engine-damaged-message': 'Il motore è danneggiato, per favore, reinstalla l\\'app : (',\n  'engine-missing-message': 'Il motore è assente, per favore, reinstalla l\\'app : (',\n  'system-error-title': 'Errore di sistema',\n  'system-error-message': 'L\\'applicazione non si è avviata: {{message}}',\n  'hide': 'Nascondi Motrix',\n  'hide-others': 'Nascondi Altro',\n  'unhide': 'Mostra Tutto',\n  'show': 'Mostra Motrix',\n  'quit': 'Esci da Motrix',\n  'under-development-message': 'Scusa, questa funzione è in fase di sviluppo...',\n  'yes': 'Si',\n  'no': 'No',\n  'save': 'Salva',\n  'reset': 'Scartare',\n  'cancel': 'Annulla',\n  'submit': 'Invia',\n  'gt1d': '> 1 giorno',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/it/edit.js",
    "content": "export default {\n  'undo': 'Annulla',\n  'redo': 'Ripeti',\n  'cut': 'Taglia',\n  'copy': 'Copia',\n  'paste': 'Incolla',\n  'delete': 'Elimina',\n  'select-all': 'Seleziona Tutto'\n}\n"
  },
  {
    "path": "src/shared/locales/it/help.js",
    "content": "export default {\n  'official-website': 'Sito di Motrix',\n  'manual': 'Manuale',\n  'release-notes': 'Note di rilascio...',\n  'report-problem': 'Segnala un\\'problema',\n  'toggle-dev-tools': 'Attiva/disatttiva gli Strumenti di sviluppo'\n}\n"
  },
  {
    "path": "src/shared/locales/it/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/it/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'File',\n  'task': 'Attività',\n  'edit': 'Modifica',\n  'window': 'Finestra',\n  'help': 'Aiuto'\n}\n"
  },
  {
    "path": "src/shared/locales/it/preferences.js",
    "content": "export default {\n  'basic': 'Base',\n  'advanced': 'Avanzate',\n  'lab': 'Sperimentali',\n  'save': 'Salva e applica',\n  'save-success-message': 'Preferenze salvate con successo',\n  'save-fail-message': 'Preferenze non salvate',\n  'discard': 'Scarta modifiche',\n  'startup': 'Avvio',\n  'open-at-login': 'Apri al login',\n  'keep-window-state': 'Mantieni le dimensioni e la posizione della finestra quando l\\'app viene chiusa',\n  'auto-resume-all': 'Ricomincia tutti le attività non finite alla riapertura dell\\'app',\n  'default-dir': 'Posizione di default per i download',\n  'mas-default-dir-tips': 'A causa delle restrizioni imposte dall\\'App Store, è raccomandato impostare la directory di default su ~/Downloads',\n  'transfer-settings': 'Transmissione dati',\n  'transfer-speed-upload': 'Limite di uplooad',\n  'transfer-speed-download': 'Limite di download',\n  'transfer-speed-unlimited': 'Illimitata',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Salva magnet link come file torrent',\n  'bt-auto-download-content': 'Scarica automaticamente il contenuto di magnete e torrent',\n  'bt-force-encryption': 'Forzare la crittografia di BT',\n  'keep-seeding': 'Continua a seminare fino a interromperlo manualmente',\n  'seed-ratio': 'Rapporto di semina',\n  'seed-time': 'Tempo di semi',\n  'seed-time-unit': 'minuti',\n  'task-manage': 'Gestione attività',\n  'max-concurrent-downloads': 'Massimo numero di attività eseguibili contemporaneamente',\n  'max-connection-per-server': 'Massimo numero di connessioni simultanee per server',\n  'new-task-show-downloading': 'Mostra automaticamente il download quando aggiungo una nuova attività',\n  'no-confirm-before-delete-task': 'Nessuna conferma richiesta prima di eliminare un\\'attività',\n  'continue': 'Continua',\n  'task-completed-notify': 'Notifica quando un download è finito',\n  'auto-purge-record': 'Elimina automaticamente la cronologia di download quando esco l\\'app viene chiusa',\n  'ui': 'UI',\n  'appearance': 'Aspetto',\n  'theme-auto': 'Auto',\n  'theme-light': 'Chiaro',\n  'theme-dark': 'Scuro',\n  'auto-hide-window': 'Nascondi automaticamente la finestra',\n  'run-mode': 'Avvia come',\n  'run-mode-standard': 'Applicazione Standard ',\n  'run-mode-tray': 'Applicazione della barra delle applicazioni',\n  'run-mode-hide-tray': 'Nascondi applicazione della barra delle applicazioni',\n  'tray-speedometer': 'La barra dei menu mostra la velocità in tempo reale',\n  'show-progress-bar': 'Mostra la barra di progresso del download',\n  'language': 'Lingua',\n  'change-language': 'Cambia lingua',\n  'hide-app-menu': 'Nascondi dal menu delle app (Solo Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Usa Proxy',\n  'proxy-bypass-input-tips': 'Non usare proxy per questi Host e Domini, uno per linea',\n  'proxy-scope-download': 'Scarica',\n  'proxy-scope-update-app': 'Aggiorna applicazione',\n  'proxy-scope-update-trackers': 'Aggiorna tracker',\n  'proxy-tips': 'Guida all\\'uso dei proxy (In Inglese)',\n  'bt-tracker': 'Server di monitoraggio',\n  'bt-tracker-input-tips': 'Tracker servers, uno per linea',\n  'bt-tracker-tips': 'Raccomandati: ',\n  'sync-tracker-tips': 'Sincronizza',\n  'auto-sync-tracker': 'Aggiorna automaticamente la lista dei tracker ogni giorno',\n  'port': 'Porte in ascolto',\n  'bt-port': 'Porte in ascolto BT',\n  'dht-port': 'Porte in ascolto DHT',\n  'security': 'Sicurezza',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Porta di Ascolto RPC',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Guida sull\\'uso degli rpc secret (in Inglese)',\n  'developer': 'Sviluppatore',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Cambia User-Agent',\n  'aria2-conf-path': 'Percorso incorporato di aria2.conf',\n  'app-log-path': 'Posizione log dell\\'app',\n  'download-session-path': 'Posizione sessione di download',\n  'factory-reset': 'Reset di fabbrica',\n  'factory-reset-confirm': 'Sei sicuro di voler riportare alle impostazioni di fabbrica l\\'app?',\n  'lab-warning': '⚠️ Ablilitare le funzioni sperimentali potrebbe risultare in un crash o una perdita di dati, decidi a tuo rischio e pericolo!',\n  'download-protocol': 'Protocolli',\n  'protocols-default-client': 'Imposta di Default i seguenti protocolli',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Estensione per browser',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Fornita dalla community, ',\n  'baidu-exporter-help': 'Clicca qui per scoprire il funzionamento',\n  'auto-update': 'Auto Update',\n  'auto-check-update': 'Verifica automaticamente la disponibilità di aggiornamenti',\n  'last-check-update-time': 'Ultima volta quando gli aggiornamenti sono stati verificati',\n  'not-saved': 'Preferenze non salvate',\n  'not-saved-confirm': 'Le preferenze modificate andranno perse, sei sicuro di uscire?'\n}\n"
  },
  {
    "path": "src/shared/locales/it/subnav.js",
    "content": "export default {\n  'task-list': 'Attività',\n  'preferences': 'Preferenze'\n}\n"
  },
  {
    "path": "src/shared/locales/it/task.js",
    "content": "export default {\n  'active': 'In corso',\n  'waiting': 'In Pausa',\n  'stopped': 'Terminate',\n  'new-task': 'Nuova attività',\n  'new-bt-task': 'Nuova attività BT',\n  'open-file': 'Apri un file Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Un url per linea (supporta magnet)',\n  'thunder-link-tips': 'Nota: I Thunder links potrebbero non essere più scaricabili dopo il decoding',\n  'new-task-uris-required': 'Per favore, inserisci almeno un url risorsa valido',\n  'new-task-torrent-required': 'Per favore, inserisci un file torrent',\n  'file-name': 'Nome',\n  'file-extension': 'Estensione',\n  'file-size': 'Dimensione',\n  'file-completed-size': 'Completato',\n  'selected-files-sum': 'Selezionati: {{selectedFilesCount}} files, dimensione totale {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Seleziona almeno un file',\n  'task-gid': 'GID',\n  'task-name': 'Nome attività',\n  'task-out': 'Rinomina',\n  'task-out-tips': '(opzionale)',\n  'task-split': 'Dividi',\n  'task-dir': 'Posizione file',\n  'pause-task': 'Metti in pausa attività',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorizzazione',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Errore',\n  'task-piece': 'Pezzo',\n  'task-piece-length': 'Dimensione del pezzo',\n  'task-num-pieces': 'Pezzi',\n  'task-bittorrent-info': 'Informazioni sul torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Data di creazione',\n  'task-bittorrent-comment': 'Commento',\n  'task-progress-info': 'Progresso',\n  'task-status': 'Stato',\n  'task-num-seeders': 'Seminatrici',\n  'task-connections': 'Connessioni',\n  'task-file-size': 'Dimensione',\n  'task-download-speed': 'Velocità di download',\n  'task-upload-speed': 'Velocità di caricamento',\n  'task-download-length': 'Scaricato',\n  'task-upload-length': 'Caricato',\n  'task-ratio': 'Rapporto',\n  'task-peer-host': 'Ospite',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Cliente',\n  'navigate-to-downloading': 'Naviga per scaricare',\n  'show-advanced-options': 'Opzioni avanzate',\n  'copyright-warning': 'Avviso sul Copyright',\n  'copyright-warning-message': 'Il file che stai cercando di scaricare potrebbe esssere un audio o video soggetto a Copyright, per favore, assicurati che tu abbia il permesso per accedervi.',\n  'copyright-yes': 'Si, ho il permesso',\n  'copyright-no': 'No, non ho il permesso',\n  'copyright-error-message': 'Impossibile scaricare il file a causa di un problema di Copyright',\n  'pause-task-success': '\"{{taskName}}\" messo in pausa con successo',\n  'pause-task-fail': 'Impossibile mettere in pausa \"{{taskName}}\"',\n  'resume-task': 'Ricomincia Task',\n  'resume-task-success': 'Attività \"{{taskName}}\" ricominciata con successo',\n  'resume-task-fail': 'Impossibile ricominciare l\\'attività: \"{{taskName}}\"',\n  'delete-task': 'Elimina attività',\n  'delete-selected-tasks': 'Elimina attività selezionate',\n  'delete-task-confirm': 'Sei sicuro di voler rimuovere l\\'attività \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Sei sicuro di voler rimuovere {{count}} attività in batch?',\n  'delete-task-label': 'Elimina con i file',\n  'delete-task-success': 'attività \"{{taskName}}\" eliminata con successo',\n  'delete-task-fail': 'Impossibile eliminare l\\'attività \"{{taskName}}\"',\n  'remove-task-file-fail': 'Imossibile eliminare i file(s) delle attività, per favore, eliminali manualmente',\n  'remove-task-config-file-fail': 'Impossibile eliminare i file di configutazine delle attività, per favore, eliminali manualmente',\n  'move-task-up': 'Muovi l\\'attività più in alto',\n  'move-task-down': 'Muovi l\\'attività più in basso',\n  'pause-all-task': 'Metti in pausa tutte le attività',\n  'pause-all-task-success': 'Tutte le attività messe in pausa con successo',\n  'pause-all-task-fail': 'Impossibile mettere in pausa tutte le attività',\n  'resume-all-task': 'Ricomincia tutte le attività',\n  'resume-all-task-success': 'Tutte le attività ricominciate con successo',\n  'resume-all-task-fail': 'Impossibile ricominciate tutte le attività',\n  'select-all-task': 'Seleziona tutte le attività',\n  'clear-recent-tasks': 'Elimina le attività recenti',\n  'purge-record': 'Elimina la cronologia delle attività',\n  'purge-record-success': 'Cronologia delle attività eliminata con successo',\n  'purge-record-fail': 'Impossibile eliminare la cronologia delle attività',\n  'batch-delete-task-success': 'Attività eliminate in batch con successo',\n  'batch-delete-task-fail': 'Impossibile eliminare in batch le attività',\n  'refresh-list': 'Aggiorna la lista delle attività',\n  'no-task': 'Non ci sono attività',\n  'copy-link': 'Copia link',\n  'copy-link-success': 'Link copiato con successo',\n  'remove-record': 'Rimuovi cronologia delle attività',\n  'remove-record-confirm': 'Sei sicuro di voler eliminare la cronologia di download di \"{{taskName}}\"?',\n  'remove-record-label': 'Elimina con i file',\n  'remove-record-success': 'cronologia dell\\'attività \"{{taskName}}\" eliminata con successo',\n  'remove-record-fail': 'Imposibile eliminare la cronologia dell\\'attività \"{{taskName}}\"',\n  'show-in-folder': 'Mostra le attività nela cartella',\n  'file-not-exist': 'File target non esistente o eliminato',\n  'file-path-error': 'Errore path del file',\n  'opening-task-message': 'Apertura attività \"{{taskName}}\" ...',\n  'get-task-name': 'Ottengo il nome del file...',\n  'remaining-prefix': 'Rimanente',\n  'select-torrent': 'Trascina il file torrent qua o clicca per selezionarlo',\n  'task-info-dialog-title': 'Dettagli {{title}}',\n  'download-start-message': 'Iniziato il download di {{taskName}}',\n  'download-pause-message': 'Download di {{taskName}} in pausa',\n  'download-stop-message': 'Download di {{taskName}} stoppato',\n  'download-error-message': 'Errore durante il download di {{taskName}}',\n  'download-complete-message': 'Dowload di {{taskName}} completato',\n  'download-complete-notify': 'Download completato',\n  'bt-download-complete-message': 'Completed downloading {{taskName}}, seeding',\n  'bt-download-complete-notify': 'Dowload BT completato, seeding...',\n  'bt-download-complete-tips': 'Suggerimento: È possibile interrompere un\\'attività per fermare il seeding',\n  'bt-stopping-seeding-tip': 'Seeding fermato, ci vorrà un po\\' di tempo per disconnettersi, per favore, aspetta...',\n  'download-fail-message': 'Impossibile scaricare {{taskName}}',\n  'download-fail-notify': 'Download Fallito'\n}\n"
  },
  {
    "path": "src/shared/locales/it/window.js",
    "content": "export default {\n  'reload': 'Ricarica',\n  'close': 'Chiudi',\n  'minimize': 'Riduci a icona',\n  'zoom': 'Zoo',\n  'toggle-fullsmcreen': 'Modalità a schermo intero',\n  'front': 'Riporta tutto davanti'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/about.js",
    "content": "export default {\n  'engine-version': 'バージョンを確認',\n  'license': 'ライセンス',\n  'about': '私たちについて',\n  'release': 'リリースノート',\n  'support': 'サポート'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/app.js",
    "content": "export default {\n  'task-list': 'タスクリスト',\n  'add-task': 'タスクを追加',\n  'about': 'Motrix について',\n  'preferences': '環境設定...',\n  'check-for-updates': '更新を確認...',\n  'check-updates-now': '今すぐチェック',\n  'checking-for-updates': 'アップデートをチェックしています...',\n  'check-for-updates-title': 'アップデートをチェック',\n  'update-available-message': '新しいバージョンのMotrixが利用可能です、今すぐ更新しますか？',\n  'update-not-available-message': 'あなたは最新です！',\n  'update-downloaded-message': 'インストールする準備ができています...',\n  'update-error-message': '更新エラー',\n  'engine-damaged-message': 'エンジンが破損しています、再インストールしてください : (',\n  'engine-missing-message': 'エンジンが見つかりません。再インストールしてください : (',\n  'system-error-title': 'システムエラー',\n  'system-error-message': 'アプリケーションの起動に失敗しました：{{message}}',\n  'hide': 'Motrix を隠す',\n  'hide-others': 'ほかを隠す',\n  'unhide': 'すべてを表示',\n  'quit': 'Motrix を終了',\n  'under-development-message': 'この機能は開発中です...',\n  'yes': 'はい',\n  'no': 'いいえ',\n  'save': 'セーブ',\n  'reset': '放棄',\n  'cancel': 'キャンセル',\n  'submit': '確認',\n  'gt1d': '一日を超える',\n  'hour': '時',\n  'minute': '分',\n  'second': '秒'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/edit.js",
    "content": "export default {\n  'undo': '元に戻す',\n  'redo': 'やり直す',\n  'cut': '切り取り',\n  'copy': 'コピー',\n  'paste': '貼り付け',\n  'delete': '削除',\n  'select-all': 'すべてを選択'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/help.js",
    "content": "export default {\n  'official-website': 'Motrix 公式サイト',\n  'manual': '使用説明',\n  'release-notes': 'リリースノート...',\n  'report-problem': '問題を報告',\n  'toggle-dev-tools': '開発者ツール'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ja/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'ファイル',\n  'task': 'タスク',\n  'edit': '編集',\n  'window': 'ウィンドウ',\n  'help': 'ヘルプ'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/preferences.js",
    "content": "export default {\n  'basic': '基本設定',\n  'advanced': '詳細設定',\n  'lab': '実験室',\n  'save': '保存して適用',\n  'save-success-message': '設定を保存します',\n  'save-fail-message': '設定を保存できませんでした',\n  'discard': '放棄',\n  'startup': '起動',\n  'open-at-login': '自動的に起動します',\n  'auto-resume-all': '起動後自動的に未完了タスクを再開',\n  'keep-window-state': 'ウィンドウのサイズと位置を元に戻します',\n  'default-dir': '既定の保存先',\n  'mas-default-dir-tips': 'App Store の Sandbox 制限のため，デフォルトディレクトリ設定は「Download」を推奨します。',\n  'transfer-settings': '転送設定',\n  'transfer-speed-upload': 'アップロード制限',\n  'transfer-speed-download': 'ダウンロード制限',\n  'transfer-speed-unlimited': '無制限',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'マグネットリンクをトレントファイルとして保存',\n  'bt-auto-download-content': '自動的に磁石と急流のコンテンツをダウンロードします',\n  'bt-force-encryption': 'BT 強制暗号化',\n  'keep-seeding': '手動で停止するまでシードを続けます',\n  'seed-ratio': 'シード比率',\n  'seed-time': 'シードタイム',\n  'seed-time-unit': '分',\n  'task-manage': 'タスク管理',\n  'max-concurrent-downloads': '最大同時タスク数',\n  'max-connection-per-server': '最大サーバ接続数',\n  'new-task-show-downloading': '新規タスクを作成後自動的にタスク画面に移る',\n  'no-confirm-before-delete-task': 'タスクを削除する前に確認は必要ありません',\n  'continue': '続ける',\n  'task-completed-notify': 'タスク完了後に通知する',\n  'auto-purge-record': 'アプリケーション終了後自動的にタスク履歴を削除',\n  'ui': 'UI',\n  'appearance': 'テーマ',\n  'theme-auto': '自動',\n  'theme-light': 'ライト',\n  'theme-dark': 'ダーク',\n  'auto-hide-window': '自動非表示ウィンドウ',\n  'run-mode': '実行者',\n  'run-mode-standard': '標準アプリケーション',\n  'run-mode-tray': 'トレイアプリケーション',\n  'run-mode-hide-tray': 'トレイアプリケーションを非表示にする',\n  'tray-speedometer': 'メニューバートレイは、リアルタイムの速度を示します',\n  'show-progress-bar': 'ダウンロード進行状況の表示',\n  'language': '言語',\n  'change-language': '言語を切り替え',\n  'hide-app-menu': 'メニューバーを隠す（Windows と Linux のみサポート）',\n  'proxy': 'プロキシ',\n  'enable-proxy': 'プロキシを使う',\n  'proxy-bypass-input-tips': 'これらのホストおよびドメインのプロキシ設定を1行に1つずつバイパスします',\n  'proxy-scope-download': 'ダウンロード',\n  'proxy-scope-update-app': 'アプリケーションの更新',\n  'proxy-scope-update-trackers': 'トラッカーを更新する',\n  'proxy-tips': 'プロキシマニュアルを表示',\n  'bt-tracker': 'トラッカーサーバー',\n  'bt-tracker-input-tips': 'トラッカーサーバ、一行に一つ',\n  'bt-tracker-tips': 'お勧め：',\n  'sync-tracker-tips': '同期する',\n  'auto-sync-tracker': 'トラッカーリストを毎日自動的に更新します',\n  'port': 'リスンポート',\n  'bt-port': 'BT リスンポート',\n  'dht-port': 'DHT リスンポート',\n  'security': 'セキュリティ',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPCリッスンポート',\n  'rpc-secret': 'RPCシークレット',\n  'rpc-secret-tips': 'RPCシークレットマニュアルの閲覧',\n  'developer': '開発者',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': '偽装ユーザーエージェント（UA）',\n  'aria2-conf-path': '組み込みの aria2.conf パス',\n  'app-log-path': 'ログディレクトリを適用',\n  'download-session-path': 'セッションパスをダウンロード',\n  'factory-reset': '初期設定に戻す',\n  'factory-reset-confirm': '本当に初期設定に戻しますか?',\n  'lab-warning': '⚠️ベータ機能をオンにするとアプリケーションの強制終了やデータが損失する可能性があります。自己責任でお願いします。',\n  'download-protocol': 'プロトコル',\n  'protocols-default-client': '以下のプロトコルのデフォルトクライアントとして設定',\n  'protocols-magnet': '磁石 [ magnet:// ]',\n  'protocols-thunder': 'サンダー [ thunder:// ]',\n  'browser-extensions': 'ブラウザ拡張機能',\n  'baidu-exporter': 'バイドゥオンラインストレージ拡張機能',\n  'browser-extensions-tips': '他のユーザによって作成されたものです。動作は保証できません。',\n  'baidu-exporter-help': 'ここをクリックし使用説明を見る',\n  'auto-update': '自動更新',\n  'auto-check-update': '更新を自動で確認する',\n  'last-check-update-time': '前回更新確認時間',\n  'not-saved': '設定が保存されていません',\n  'not-saved-confirm': '変更された設定は失われます、よろしいですか？'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/subnav.js",
    "content": "export default {\n  'task-list': 'タスクリスト',\n  'preferences': '環境設定'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/task.js",
    "content": "export default {\n  'active': 'ダウンロード中',\n  'waiting': '待機中',\n  'stopped': '一時停止中',\n  'new-task': '新規タスク',\n  'new-bt-task': '新規torrentタスク',\n  'open-file': 'torrentファイルを開く...',\n  'uri-task': 'URLタスク',\n  'torrent-task': 'torrentタスク',\n  'uri-task-tips': 'URLを複数追加したとき、一行につき一つのリンクとなります(マグネットリンクをサポート)',\n  'thunder-link-tips': '注意：Thunder(Xunlei)リンク解析後、ダウンロードできるかは保証できません',\n  'new-task-uris-required': '少なくとも1つの有効なリソースURLを入力してください',\n  'new-task-torrent-required': 'トレントファイルを選択してください',\n  'file-name': 'ファイル名',\n  'file-extension': '拡張子',\n  'file-size': 'サイズ',\n  'file-completed-size': '完成サイズ',\n  'selected-files-sum': '選択済み：{{selectedFilesCount}}ファイル、合計{{selectedFilesTotalSize}}',\n  'select-at-least-one': '少なくとも1つのファイルを選択してください',\n  'task-gid': 'GID',\n  'task-name': 'タスク名',\n  'task-out': '名前を変更',\n  'task-out-tips': 'オプション',\n  'task-split': 'タスク分割',\n  'task-dir': 'タスク保存先',\n  'task-ua': 'UA',\n  'task-user-agent': 'ユーザーエージェント',\n  'task-authorization': '認可',\n  'task-referer': 'リファラ',\n  'task-cookie': 'クッキー',\n  'task-proxy': 'プロキシ',\n  'task-error-info': 'エラー',\n  'task-piece': 'ピース',\n  'task-piece-length': 'ピースサイズ',\n  'task-num-pieces': 'ピース',\n  'task-bittorrent-info': 'トレント情報',\n  'task-info-hash': 'ハッシュ',\n  'task-bittorrent-creation-date': '作成日',\n  'task-bittorrent-comment': 'コメント',\n  'task-progress-info': '進捗',\n  'task-status': '状態',\n  'task-num-seeders': 'シーダー',\n  'task-connections': '接続',\n  'task-file-size': 'サイズ',\n  'task-download-speed': 'ダウンロード速度',\n  'task-upload-speed': 'アップロードスピード',\n  'task-download-length': 'ダウンロード済み',\n  'task-upload-length': 'アップロード済み',\n  'task-ratio': '播種率',\n  'task-peer-host': 'ホスト',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'クライアント',\n  'navigate-to-downloading': 'タスク画面に進む',\n  'show-advanced-options': '詳細設定',\n  'copyright-warning': '著作権警告',\n  'copyright-warning-message': 'あなたがダウンロードしようとしているファイルは著作権のある音声・動画の可能性があります。著作権があるかどうか確認してください',\n  'copyright-yes': 'はい、著作権があります',\n  'copyright-no': 'いいえ',\n  'copyright-error-message': '著作権の問題で、タスク追加に失敗',\n  'pause-task': 'タスクを一時停止',\n  'pause-task-success': 'タスク \"{{taskName}}\" の一時停止に成功',\n  'pause-task-fail': 'タスク \"{{taskName}}\" の一時停止に失敗',\n  'resume-task': 'タスクを再開',\n  'resume-task-success': 'タスク \"{{taskName}}\" の再開に成功',\n  'resume-task-fail': '恢复任务 \"{{taskName}}\" の再開に失敗',\n  'delete-task': 'タスクを削除',\n  'delete-selected-tasks': '選択中のタスクを削除',\n  'delete-task-confirm': '本当にタスク \"{{taskName}}\" を削除しますか?',\n  'batch-delete-task-confirm': '{{count}}つのダウンロードタスクをバッチで削除してもよろしいですか？',\n  'delete-task-label': '同時にタスクを削除',\n  'delete-task-success': 'タスク \"{{taskName}}\" の削除に成功',\n  'delete-task-fail': 'タスク \"{{taskName}}\" の削除に失敗',\n  'remove-task-file-fail': 'ダウンロードファイルの削除に失敗、手動で削除してください',\n  'remove-task-config-file-fail': 'タスク設定ファイルの削除に失敗、手動で削除してください',\n  'move-task-up': 'タスクを上に移す',\n  'move-task-down': 'タスクを下に移す',\n  'pause-all-task': 'すべてのタスクを一時停止',\n  'pause-all-task-success': 'すべてのタスクの一時停止に成功',\n  'pause-all-task-fail': 'すべてのタスクの一時停止に失敗',\n  'resume-all-task': 'すべてのタスクを再開',\n  'resume-all-task-success': 'すべてのタスクを再開に成功',\n  'resume-all-task-fail': 'すべてのタスクを再開に失敗',\n  'select-all-task': 'すべてのタスクを選択します',\n  'clear-recent-tasks': '最近のタスク履歴を削除',\n  'purge-record': 'タスク履歴をクリア',\n  'purge-record-success': 'タスク履歴のクリアに成功',\n  'purge-record-fail': 'タスク履歴のクリアに失敗',\n  'batch-delete-task-success': 'タスクをバッチで正常に削除しました',\n  'batch-delete-task-fail': 'タスクをバッチで削除できませんでした',\n  'refresh-list': 'タスクリストを更新',\n  'no-task': 'タスクはありません',\n  'copy-link': 'リンクをコピー',\n  'copy-link-success': 'リンクのコピーに成功',\n  'remove-record': 'タスク履歴を削除',\n  'remove-record-confirm': '本当に \"{{taskName}}\" のタスク履歴を削除しますか?',\n  'remove-record-label': 'ダウンロードファイルも同時に削除',\n  'remove-record-success': ' \"{{taskName}}\" のタスク履歴の削除に成功',\n  'remove-record-fail': ' \"{{taskName}}\" のタスク履歴の削除に失敗',\n  'show-in-folder': 'フォルダを表示',\n  'file-not-exist': 'お探しのファイルは存在しないか、削除されています。',\n  'file-path-error': 'ファイルパスエラー',\n  'opening-task-message': '現在 \"{{taskName}}\" を開いています...',\n  'get-task-name': 'タスク名を取得しています...',\n  'remaining-prefix': '残り',\n  'select-torrent': 'torrentファイルをドラッグ＆ドロップするか、ここをクリック',\n  'task-info-dialog-title': '{{title}} の詳細',\n  'download-start-message': '{{taskName}}のダウンロードを開始',\n  'download-pause-message': '{{taskName}}のダウンロードを一時停止',\n  'download-stop-message': '{{taskName}} のダウンロードを中止',\n  'download-error-message': '{{taskName}} のダウンロードにエラーが発生',\n  'download-complete-message': '{{taskName}} のダウンロードに成功',\n  'download-complete-notify': 'ダウンロード成功',\n  'bt-download-complete-message': '{{taskName}} のダウンロードに成功、seedを作成中...',\n  'bt-download-complete-notify': 'torrentタスクのダウンロードに成功，seedを作成中...',\n  'bt-download-complete-tips': 'ヒント：タスクを停止しseedの作成を終了することができます',\n  'bt-stopping-seeding-tip': 'シードを停止しています。切断するにはしばらく時間がかかります。お待ちください...',\n  'download-fail-message': '{{taskName}} のダウンロードに失敗',\n  'download-fail-notify': 'ダウンロード失敗'\n}\n"
  },
  {
    "path": "src/shared/locales/ja/window.js",
    "content": "export default {\n  'reload': 'リロード',\n  'close': '閉じる',\n  'minimize': '最小化',\n  'zoom': '拡大',\n  'toggle-fullscreen': 'フルスクリーンにする',\n  'front': '全てを手前に移動'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/about.js",
    "content": "export default {\n  'engine-version': '엔진 버전',\n  'license': '라이선스',\n  'about': '정보',\n  'release': '릴리스',\n  'support': '지원'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/app.js",
    "content": "export default {\n  'task-list': '작업 목록',\n  'add-task': '작업 추가',\n  'about': 'Motrix 정보',\n  'preferences': '설정...',\n  'check-for-updates': '업데이트 확인...',\n  'check-updates-now': '지금 확인',\n  'checking-for-updates': '업데이트 확인 중...',\n  'check-for-updates-title': '업데이트 확인',\n  'update-available-message': '새 버전의 Motrix를 사용할 수 있습니다. 지금 업데이트하시겠습니까?',\n  'update-not-available-message': '최신 버전을 사용 중입니다!',\n  'update-downloaded-message': '설치 준비 완료...',\n  'update-error-message': '업데이트 오류',\n  'engine-damaged-message': '엔진이 손상되었으므로 다시 설치하십시오: (',\n  'engine-missing-message': '엔진이 누락되었으므로 다시 설치하십시오: (',\n  'system-error-title': '시스템 오류',\n  'system-error-message': '애플리케이션 시작 실패: {{message}}',\n  'hide': 'Motrix 숨기기',\n  'hide-others': '다른 항목 숨기기',\n  'unhide': '모두 보기',\n  'show': 'Motrix 보기',\n  'quit': 'Motrix 종료',\n  'under-development-message': '죄송합니다, 이 기능은 개발 중입니다...',\n  'yes': '예',\n  'no': '아니요',\n  'save': '저장',\n  'reset': '취소',\n  'cancel': '취소',\n  'submit': '확인',\n  'gt1d': '> 1일',\n  'hour': '시간',\n  'minute': '분',\n  'second': '초'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/edit.js",
    "content": "export default {\n  'undo': '실행 취소',\n  'redo': '다시 실행',\n  'cut': '잘라내기',\n  'copy': '복사',\n  'paste': '붙여넣기',\n  'delete': '삭제',\n  'select-all': '모두 선택'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/help.js",
    "content": "export default {\n  'official-website': 'Motrix 웹 사이트',\n  'manual': '매뉴얼',\n  'release-notes': '릴리스 정보...',\n  'report-problem': '문제 보고',\n  'toggle-dev-tools': '개발자 도구'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ko/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': '파일',\n  'task': '작업',\n  'edit': '편집',\n  'window': '창',\n  'help': '도움말'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/preferences.js",
    "content": "export default {\n  'basic': '기본',\n  'advanced': '고급',\n  'lab': '실험실',\n  'save': '저장 및 적용',\n  'save-success-message': '설정을 성공적으로 저장했습니다',\n  'save-fail-message': '설정을 저장하지 못했습니다',\n  'discard': '취소',\n  'startup': '시작',\n  'open-at-login': '로그인 시 실행',\n  'keep-window-state': '창 크기 및 위치 기억',\n  'auto-resume-all': '완료되지 않은 모든 작업 자동 재개',\n  'default-dir': '기본 폴더',\n  'mas-default-dir-tips': 'App Store의 샌드박스 권한 제한으로 인해 기본 다운로드 폴더는 ~/Downloads로 설정하는 것이 좋습니다.',\n  'transfer-settings': '전송',\n  'transfer-speed-upload': '업로드 제한',\n  'transfer-speed-download': '다운로드 제한',\n  'transfer-speed-unlimited': '무제한',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': '마그넷 링크를 토렌트 파일로 저장',\n  'bt-auto-download-content': '마그넷 및 토렌트 내용 자동 다운로드',\n  'bt-force-encryption': 'BT 강제 암호화',\n  'keep-seeding': '수동으로 멈출 때까지 계속 배포',\n  'seed-ratio': '배포 비율',\n  'seed-time': '배포 시간',\n  'seed-time-unit': '분',\n  'task-manage': '작업 관리',\n  'max-concurrent-downloads': '최대 활성 작업',\n  'max-connection-per-server': '서버 당 최대 연결 수',\n  'new-task-show-downloading': '작업 추가 후 자동으로 다운로드 표시',\n  'no-confirm-before-delete-task': '작업을 삭제하기 전에 확인하지 않기',\n  'continue': '계속',\n  'task-completed-notify': '다운로드 완료 후 알림',\n  'auto-purge-record': '앱 종료 시 다운로드 기록 자동 삭제',\n  'ui': 'UI',\n  'appearance': '테마',\n  'theme-auto': '자동',\n  'theme-light': '밝게',\n  'theme-dark': '어둡게',\n  'auto-hide-window': '창 자동으로 숨기기',\n  'run-mode': '실행 모드',\n  'run-mode-standard': '표준 애플리케이션',\n  'run-mode-tray': '트레이 애플리케이션',\n  'run-mode-hide-tray': '트레이 응용 프로그램 숨기기',\n  'tray-speedometer': '메뉴 막대 트레이에 실시간 속도가 표시됩니다',\n  'show-progress-bar': '다운로드 진행률 막대기 보이기',\n  'language': '언어',\n  'change-language': '언어 변경',\n  'hide-app-menu': '앱 메뉴 숨기기 (Windows 및 Linux)',\n  'proxy': '프록시',\n  'enable-proxy': '프록시 사용',\n  'proxy-bypass-input-tips': '프록시 설정을 우회할 호스트 및 도메인 (한 줄에 하나)',\n  'proxy-scope-download': '다운로드',\n  'proxy-scope-update-app': '애플리케이션 업데이트',\n  'proxy-scope-update-trackers': '추적기 업데이트',\n  'proxy-tips': '프록시 매뉴얼 보기',\n  'bt-tracker': '트래커 서버',\n  'bt-tracker-input-tips': '트래커 서버 (한 줄에 하나)',\n  'bt-tracker-tips': '권장: ',\n  'sync-tracker-tips': '동기화',\n  'auto-sync-tracker': '매일 자동으로 트래커 목록 업데이트',\n  'port': '청취 포트',\n  'bt-port': 'BT 청취 포트',\n  'dht-port': 'DHT 청취 포트',\n  'security': '보안',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC 청취 포트',\n  'rpc-secret': 'RPC 비밀',\n  'rpc-secret-tips': 'RPC 비밀 매뉴얼 보기',\n  'developer': '개발자',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': '모의 사용자 에이전트',\n  'aria2-conf-path': '내장 된 aria2.conf 경로',\n  'app-log-path': '앱 로그 경로',\n  'download-session-path': '다운로드 세션 경로',\n  'session-reset': '다운로드 세션 초기화',\n  'session-reset-confirm': '다운로드 세션을 초기화하시겠습니까?',\n  'factory-reset': '공장 초기화',\n  'factory-reset-confirm': '초기 설정으로 되돌리시겠습니까?',\n  'lab-warning': '⚠️ 실험실 기능을 사용 설정하면 앱 충돌 또는 데이터 손실이 발생할 수 있으므로 신중히 결정하십시오!',\n  'download-protocol': '프로토콜',\n  'protocols-default-client': '다음 프로토콜의 기본 클라이언트로 설정',\n  'protocols-magnet': '마그넷 [ magnet:// ]',\n  'protocols-thunder': '썬더 [ thunder:// ]',\n  'browser-extensions': '확장 프로그램',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': '커뮤니티에서 제공하는, ',\n  'baidu-exporter-help': '이곳을 클릭하여 사용법 확인',\n  'auto-update': '자동 업데이트',\n  'auto-check-update': '자동으로 업데이트 확인',\n  'last-check-update-time': '마지막 업데이트 확인 시간',\n  'not-saved': '설정이 저장되지 않았습니다',\n  'not-saved-confirm': '수정된 설정이 손실됩니다. 나가시겠습니까?'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/subnav.js",
    "content": "export default {\n  'task-list': '작업',\n  'preferences': '설정'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/task.js",
    "content": "export default {\n  'active': '다운로드 중',\n  'waiting': '대기 중',\n  'stopped': '중단됨',\n  'new-task': '새 작업',\n  'new-bt-task': '새 BT 작업',\n  'open-file': '토렌트 파일 열기...',\n  'uri-task': 'URL',\n  'torrent-task': '토렌트',\n  'uri-task-tips': '한 줄에 작업 URL 하나 (마그넷 지원)',\n  'thunder-link-tips': '도움말: 디코딩 후 썬더 링크가 다운로드되지 않을 수 있습니다',\n  'new-task-uris-required': '하나 이상의 유효한 리소스 URL을 입력하십시오',\n  'new-task-torrent-required': '토렌트 파일을 선택하십시오',\n  'file-name': '파일 이름',\n  'file-extension': '파일 확장자',\n  'file-size': '파일 크기',\n  'file-completed-size': '전체 크기',\n  'selected-files-sum': '파일 {{selectedFilesCount}}개 선택됨, 총 {{selectedFilesTotalSize}}',\n  'select-at-least-one': '하나 이상의 파일을 선택하십시오',\n  'task-gid': 'GID',\n  'task-name': '작업 이름',\n  'task-out': '이름 변경',\n  'task-out-tips': '선택',\n  'task-split': '분할',\n  'task-dir': '폴더',\n  'pause-task': '작업 일시정지',\n  'task-ua': 'UA',\n  'task-user-agent': '사용자 에이전트',\n  'task-authorization': '권한 부여',\n  'task-referer': '리퍼러',\n  'task-cookie': '쿠키',\n  'task-proxy': '프록시',\n  'task-error-info': '오류',\n  'task-piece': '조각',\n  'task-piece-length': '조각 크기',\n  'task-num-pieces': '조각',\n  'task-bittorrent-info': '토렌트 정보',\n  'task-info-hash': '해시',\n  'task-bittorrent-creation-date': '제작일',\n  'task-bittorrent-comment': '코멘트',\n  'task-progress-info': '진행',\n  'task-status': '상태',\n  'task-num-seeders': '시더',\n  'task-connections': '연결',\n  'task-file-size': '크기',\n  'task-download-speed': '다운로드 속도',\n  'task-upload-speed': '업로드 속도',\n  'task-download-length': '다운로드됨',\n  'task-upload-length': '업로드됨',\n  'task-ratio': '비율',\n  'task-peer-host': '호스트',\n  'task-peer-ip': 'IP',\n  'task-peer-client': '클라이언트',\n  'navigate-to-downloading': '다운로드로 이동',\n  'show-advanced-options': '고급 옵션',\n  'copyright-warning': '저작권 경고',\n  'copyright-warning-message': '다운로드하려는 파일은 저작권이 있는 오디오 또는 비디오일 수 있으므로 사용 권한이 있는지 확인하십시오.',\n  'copyright-yes': '예, 권한이 있습니다',\n  'copyright-no': '아니요, 권한이 없습니다',\n  'copyright-error-message': '저작권 문제로 인해 작업 추가 실패',\n  'pause-task-success': '\"{{taskName}}\" 작업 일시정지 성공',\n  'pause-task-fail': '\"{{taskName}}\" 작업 일시정지 실패',\n  'resume-task': '작업 재개',\n  'resume-task-success': '\"{{taskName}}\" 작업 재개 성공',\n  'resume-task-fail': '\"{{taskName}}\" 작업 재개 실패',\n  'delete-task': '작업 삭제',\n  'delete-selected-tasks': '선택된 작업 삭제',\n  'delete-task-confirm': '\"{{taskName}}\" 다운로드 작업을 삭제하시겠습니까?',\n  'batch-delete-task-confirm': '{{count}}개의 다운로드 작업을 일괄 삭제하시겠습니까?',\n  'delete-task-label': '파일을 같이 삭제',\n  'delete-task-success': '\"{{taskName}}\" 작업 삭제 성공',\n  'delete-task-fail': '\"{{taskName}}\" 작업 삭제 실패',\n  'remove-task-file-fail': '작업 파일을 제거하지 못했으므로 수동으로 제거하십시오.',\n  'remove-task-config-file-fail': '작업 구성 파일을 제거하지 못했으므로 수동으로 제거하십시오.',\n  'move-task-up': '작업을 위로 이동',\n  'move-task-down': '작업을 아래로 이동',\n  'pause-all-task': '모든 작업 일시정지',\n  'pause-all-task-success': '모든 작업 일시정지 성공',\n  'pause-all-task-fail': '모든 작업 일시정지 실패',\n  'resume-all-task': '모든 작업 재개',\n  'resume-all-task-success': '모든 작업 재개 성공',\n  'resume-all-task-fail': '모든 작업 재개 실패',\n  'select-all-task': '모든 작업 선택',\n  'clear-recent-tasks': '최근 작업 지우기',\n  'purge-record': '작업 기록 삭제',\n  'purge-record-success': '작업 기록 삭제 성공',\n  'purge-record-fail': '작업 기록 삭제 실패',\n  'batch-delete-task-success': '작업 일괄 삭제 성공',\n  'batch-delete-task-fail': '작업 일괄 삭제 실패',\n  'refresh-list': '작업 목록 새로 고침',\n  'no-task': '현재 다운로드 없음',\n  'copy-link': '링크 복사',\n  'copy-link-success': '링크 복사 성공',\n  'remove-record': '작업 기록 제거',\n  'remove-record-confirm': '\"{{taskName}}\" 다운로드 기록을 제거하시겠습니까?',\n  'remove-record-label': '파일을 같이 삭제',\n  'remove-record-success': '\"{{taskName}}\" 작업 기록 제거 성공',\n  'remove-record-fail': '\"{{taskName}}\" 작업 기록 제거 실패',\n  'show-in-folder': '작업 폴더 표시',\n  'file-not-exist': '파일이 존재하지 않거나 삭제되었습니다',\n  'file-path-error': '파일 경로 오류',\n  'opening-task-message': '\"{{taskName}}\" 여는 중...',\n  'get-task-name': '작업 이름 가져오기...',\n  'remaining-prefix': '남음',\n  'select-torrent': '토렌트 파일을 여기로 드래그하거나 클릭해서 선택하십시오',\n  'task-detail-title': '작업 정보',\n  'task-info-dialog-title': '{{title}} 정보',\n  'download-start-message': '{{taskName}} 다운로드 시작',\n  'download-pause-message': '{{taskName}} 다운로드 일시정지',\n  'download-stop-message': '{{taskName}} 다운로드 중지',\n  'download-error-message': '{{taskName}} 다운로드 오류 발생',\n  'download-complete-message': '{{taskName}} 다운로드 완료',\n  'download-complete-notify': '다운로드 완료',\n  'bt-download-complete-message': '{{taskName}} 다운로드 완료, 배포 중',\n  'bt-download-complete-notify': 'BT 다운로드 완료, 배포 중...',\n  'bt-download-complete-tips': '도움말: 작업을 중지하여 배포를 종료할 수 있습니다',\n  'bt-stopping-seeding-tip': '배포를 중지하면 연결을 끊는 데 시간이 걸립니다. 잠시 기다려 주십시오...',\n  'download-fail-message': '{{taskName}} 다운로드 실패',\n  'download-fail-notify': '다운로드 실패'\n}\n"
  },
  {
    "path": "src/shared/locales/ko/window.js",
    "content": "export default {\n  'reload': '다시 불러오기',\n  'close': '닫기',\n  'minimize': '최소화',\n  'zoom': '확대',\n  'toggle-fullscreen': '전체 화면',\n  'front': '모두 앞으로 가져오기'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/about.js",
    "content": "export default {\n  'engine-version': 'Motorversjon',\n  'license': 'Tillatelse',\n  'about': 'Om',\n  'release': 'Utgivelser',\n  'support': 'Brukerstøtte'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/app.js",
    "content": "export default {\n  'task-list': 'Oppgaver',\n  'add-task': 'Legg til oppgave',\n  'about': 'Om Motrix',\n  'preferences': 'Preferanser...',\n  'check-for-updates': 'Se etter oppdateringer...',\n  'check-updates-now': 'Sjekk nå',\n  'checking-for-updates': 'Ser etter oppdateringer...',\n  'check-for-updates-title': 'Se etter oppdateringer',\n  'update-available-message': 'En nyere versjon av Motrix er tilgjengelig, oppdater nå?',\n  'update-not-available-message': 'Du er oppdatert!',\n  'update-downloaded-message': 'Klar til å installere...',\n  'update-error-message': 'Oppdateringsfeil',\n  'engine-damaged-message': 'Motoren er skadet. Vennligst installer den på nytt :(',\n  'engine-missing-message': 'Motoren mangler. Vennligst installer den på nytt :(',\n  'system-error-title': 'Systemfeil',\n  'system-error-message': 'Oppstart av applikasjon mislyktes: {{message}}',\n  'hide': 'Skjul Motrix',\n  'hide-others': 'Skjul andre',\n  'unhide': 'Vis alt',\n  'show': 'Vis Motrix',\n  'quit': 'Avslutt Motrix',\n  'under-development-message': 'Beklager, denne funksjonen er under utvikling...',\n  'yes': 'Ja',\n  'no': 'Nei',\n  'save': 'Lagre',\n  'reset': 'Nullstill',\n  'cancel': 'Avbryt',\n  'submit': 'Send inn',\n  'gt1d': '> 1 dag',\n  'hour': 't',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/edit.js",
    "content": "export default {\n  'undo': 'Angre',\n  'redo': 'Utfør igjen',\n  'cut': 'Klipp',\n  'copy': 'Kopier',\n  'paste': 'Lim inn',\n  'delete': 'Fjern',\n  'select-all': 'Velg alle'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/help.js",
    "content": "export default {\n  'official-website': 'Motrix\\' nettside',\n  'manual': 'Brukerhåndbok',\n  'release-notes': 'Utgivelsesnotater...',\n  'report-problem': 'Rapporter problem',\n  'toggle-dev-tools': 'Vis/skjul utviklerverktøy'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/nb/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Fil',\n  'task': 'Oppgave',\n  'edit': 'Rediger',\n  'window': 'Vindu',\n  'help': 'Hjelp'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/preferences.js",
    "content": "export default {\n  'basic': 'Grunnleggende',\n  'advanced': 'Avansert',\n  'lab': 'Lab',\n  'save': 'Lagre og bruk',\n  'save-success-message': 'Lagre innstillinger vellykket',\n  'save-fail-message': 'Lagring av innstillinger mislyktes',\n  'discard': 'Forkast',\n  'startup': 'Oppstart',\n  'open-at-login': 'Åpne ved pålogging',\n  'keep-window-state': 'Behold vinduets størrelse og posisjon når du går ut',\n  'auto-resume-all': 'Gjenoppta automatisk alle uferdige oppgaver',\n  'default-dir': 'Standard bane',\n  'mas-default-dir-tips': 'På grunn av begrensninger for tillatelse av sandkasse i App Store, anbefales standard nedlastningskatalog å settes til ~ / Nedlastinger',\n  'transfer-settings': 'Overføring',\n  'transfer-speed-upload': 'Opplastingsgrense',\n  'transfer-speed-download': 'Nedlastingsgrense',\n  'transfer-speed-unlimited': 'Ubegrenset',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Lagre magnetkobling som torrentfil',\n  'bt-auto-download-content': 'Last ned Magnet og Torrent-innholdet automatisk',\n  'bt-force-encryption': 'TVANGSKRYPTERING AV BT',\n  'keep-seeding': 'Fortsett seeding til du stopper manuelt',\n  'seed-ratio': 'Seed-forhold',\n  'seed-time': 'Seed-tid',\n  'seed-time-unit': 'minutter',\n  'task-manage': 'Oppgavebehandling',\n  'max-concurrent-downloads': 'Maksimum aktive oppgaver',\n  'max-connection-per-server': 'Maksimal tilkobling per server',\n  'new-task-show-downloading': 'Vis nedlasting automatisk etter at oppgaven er lagt til',\n  'no-confirm-before-delete-task': 'Ingen bekreftelse er nødvendig før du sletter oppgaven',\n  'continue': 'Fortsette',\n  'task-completed-notify': 'Varsling etter nedlasting er fullført',\n  'auto-purge-record': 'Tøm nedlastingsposter automatisk når du avslutter appen',\n  'ui': 'UI',\n  'appearance': 'Utseende',\n  'theme-auto': 'Auto',\n  'theme-light': 'Lys',\n  'theme-dark': 'Mørk',\n  'auto-hide-window': 'Skjul automatisk vindu',\n  'run-mode': 'Applikasjonsmodus',\n  'run-mode-standard': 'Standard applikasjon',\n  'run-mode-tray': 'Tray-applikasjon',\n  'run-mode-hide-tray': 'Skjul statusfeltprogram',\n  'tray-speedometer': 'Menylinje viser sanntidshastighet',\n  'show-progress-bar': 'Vis fremgangslinjen for nedlastning',\n  'language': 'Språk',\n  'change-language': 'Skifte språk',\n  'hide-app-menu': 'Skjul appmeny (kun Windows og Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Aktiver proxy',\n  'proxy-bypass-input-tips': 'Omgå proxy-innstillinger for disse vertene og domenene, en per linje',\n  'proxy-scope-download': 'Nedlasting',\n  'proxy-scope-update-app': 'Oppdater applikasjonen',\n  'proxy-scope-update-trackers': 'Oppdater sporingskapsler',\n  'proxy-tips': 'Se Proxy Manual',\n  'bt-tracker': 'Tracker-servere',\n  'bt-tracker-input-tips': 'Tracker-servere, en per linje',\n  'bt-tracker-tips': 'Anbefalt:',\n  'sync-tracker-tips': 'Synkroniser',\n  'auto-sync-tracker': 'Oppdater trackerlisten hver dag automatisk',\n  'port': 'Lytt på porter',\n  'bt-port': 'BT-lytteport',\n  'dht-port': 'DHT-lytteport',\n  'security': 'Sikkerhet',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC lytteport',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Se RPC Secret Manual',\n  'developer': 'Utvikler',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Innebygd aria2.conf-sti',\n  'app-log-path': 'Apploggbane',\n  'download-session-path': 'Last ned øktstien',\n  'session-reset': 'Tilbakestill nedlastingsøkten',\n  'session-reset-confirm': 'Er du sikker på at du vil tilbakestille nedlastingsøkten?',\n  'factory-reset': 'Fabrikkinnstillinger',\n  'factory-reset-confirm': 'Er du sikker på at du vil gå tilbake til fabrikkinnstillingene?',\n  'lab-warning': '⚠️ Aktivering av laboratoriefunksjoner kan føre til appkrasj eller tap av data, gjør det på egen risiko!',\n  'download-protocol': 'Protokoller',\n  'protocols-default-client': 'Angi som standardklient for følgende protokoller',\n  'protocols-magnet': 'Magnet [magnet: //]',\n  'protocols-thunder': 'Torden [torden: //]',\n  'browser-extensions': 'Utvidelser',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Levert av samfunnet,',\n  'baidu-exporter-help': 'Klikk her for bruk',\n  'auto-update': 'Automatisk oppdatering',\n  'auto-check-update': 'Sjekk automatisk for oppdatering',\n  'last-check-update-time': 'Siste gang sjekket for oppdatering',\n  'not-saved': 'Preferansene er ikke lagret',\n  'not-saved-confirm': 'De endrede preferansene vil gå tapt, er du sikker på at du vil forlate?'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/subnav.js",
    "content": "export default {\n  'task-list': 'Oppgaver',\n  'preferences': 'Innstillinger'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/task.js",
    "content": "export default {\n  'active': 'Laster ned',\n  'waiting': 'Venter',\n  'stopped': 'Stoppet',\n  'new-task': 'Ny oppgave',\n  'new-bt-task': 'Ny BT-oppgave',\n  'open-file': 'Åpne torrentfil...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Én oppgave-URL per linje (støtter magnet)',\n  'thunder-link-tips': 'Tips: Tordenlinker kan ikke lastes ned etter dekoding',\n  'new-task-uris-required': 'Angi minst én gyldig ressurs-URL',\n  'new-task-torrent-required': 'Velg en torrentfil',\n  'file-name': 'Navn',\n  'file-extension': 'Filetternavn',\n  'file-size': 'Størrelse',\n  'file-completed-size': 'Fullført',\n  'selected-files-sum': 'Valgt: {{selectedFilesCount}} filer, total størrelse {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Velg minst én fil',\n  'task-gid': 'GID',\n  'task-name': 'Oppgavenavn',\n  'task-out': 'Gi nytt navn',\n  'task-out-tips': 'Valgfri',\n  'task-split': 'Deler',\n  'task-dir': 'Lagre til',\n  'pause-task': 'Pause oppgave',\n  'task-ua': 'UA',\n  'task-user-agent': 'Brukeragent',\n  'task-authorization': 'Autorisasjon',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Fullmektig',\n  'task-error-info': 'Feil',\n  'task-piece': 'Stykke',\n  'task-piece-length': 'Stykkestørrelse',\n  'task-num-pieces': 'Stykker',\n  'task-bittorrent-info': 'Torrentinformasjon',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Opprettelsesdato',\n  'task-bittorrent-comment': 'Kommentar',\n  'task-progress-info': 'Framgang',\n  'task-status': 'Status',\n  'task-num-seeders': 'Seeders',\n  'task-connections': 'Tilkoblinger',\n  'task-file-size': 'Størrelse',\n  'task-download-speed': 'Nedlastningshastighet',\n  'task-upload-speed': 'Opplastningshastighet',\n  'task-download-length': 'Lastet ned',\n  'task-upload-length': 'Lastet opp',\n  'task-ratio': 'Forhold',\n  'task-peer-host': 'Vert',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Klient',\n  'navigate-to-downloading': 'Naviger til Nedlasting',\n  'show-advanced-options': 'Avanserte instillinger',\n  'copyright-warning': 'Copyright Advarsel',\n  'copyright-warning-message': 'Filen du vil laste ned kan være lydbeskyttet eller beskyttet av copyright, vær sikker på at du har tillatelse til å få tilgang til den.',\n  'copyright-yes': 'Ja, jeg har tillatelse',\n  'copyright-no': 'Nei, jeg har ikke tillatelse',\n  'copyright-error-message': 'Kunne ikke legge til oppgave på grunn av problem med opphavsrett',\n  'pause-task-success': 'Oppgave \"{{taskName}}\" er midlertidig stoppet',\n  'pause-task-fail': 'Kunne ikke pause oppgaven \"{{taskName}}\"',\n  'resume-task': 'Fortsett oppgave',\n  'resume-task-success': 'Oppgaven \"{{taskName}}\" ble gjenopptatt',\n  'resume-task-fail': 'Kunne ikke gjenoppta oppgaven \"{{taskName}}\"',\n  'delete-task': 'Slett oppgave',\n  'delete-selected-tasks': 'Slett valgte oppgaver',\n  'delete-task-confirm': 'Er du sikker på at du vil fjerne nedlastingsoppgaven \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Er du sikker på at du vil fjerne {{count}} nedlastingsoppgaver i batch?',\n  'delete-task-label': 'Slett med filer',\n  'delete-task-success': 'Oppgave \"{{taskName}}\" er slettet',\n  'delete-task-fail': 'Kunne ikke slette oppgaven \"{{taskName}}\"',\n  'remove-task-file-fail': 'Kunne ikke slette oppgavefil (er), vennligst slett dem manuelt',\n  'remove-task-config-file-fail': 'Kunne ikke slette oppgavekonfigurasjonsfilen. Slett den manuelt',\n  'move-task-up': 'Flytt oppgaven',\n  'move-task-down': 'Flytt oppgave ned',\n  'pause-all-task': 'Sett alle oppgaver på pause',\n  'pause-all-task-success': 'Pause alle oppgaver',\n  'pause-all-task-fail': 'Kunne ikke stoppe alle oppgaver midlertidig',\n  'resume-all-task': 'Fortsett alle oppgaver',\n  'resume-all-task-success': 'Gjenopptatt alle oppgaver',\n  'resume-all-task-fail': 'Kunne ikke gjenoppta alle oppgaver',\n  'select-all-task': 'Velg alle oppgaver',\n  'clear-recent-tasks': 'Fjern siste oppgaver',\n  'purge-record': 'Tøm oppgavelogg',\n  'purge-record-success': 'Tømming av oppgavelogger var vellykket',\n  'purge-record-fail': 'Kunne ikke tømme oppgavelogger',\n  'batch-delete-task-success': 'Slett oppgaver i batch',\n  'batch-delete-task-fail': 'Kunne ikke slette oppgaver i batch',\n  'refresh-list': 'Oppdater oppgaveliste',\n  'no-task': 'Det er ingen aktuelle oppgaver',\n  'copy-link': 'Kopier link',\n  'copy-link-success': 'Koblingen ble kopiert',\n  'remove-record': 'Fjern oppgaveliste',\n  'remove-record-confirm': 'Er du sikker på at du vil fjerne nedlastingsposten for \"{{taskName}}\"?',\n  'remove-record-label': 'Slett med filer',\n  'remove-record-success': 'Nedlastingsposten for \"{{taskName}}\" er fjernet',\n  'remove-record-fail': 'Kunne ikke fjerne nedlastingsposten for \"{{taskName}}\"',\n  'show-in-folder': 'Vis oppgave i mappe',\n  'file-not-exist': 'Målfilen eksisterer ikke eller er slettet',\n  'file-path-error': 'Filsti feil',\n  'opening-task-message': 'Åpner \"{{taskName}}\"...',\n  'get-task-name': 'Henter oppgavens navn...',\n  'remaining-prefix': 'Gjenstående',\n  'select-torrent': 'Dra torrentfil hit, eller klikk for å velge',\n  'task-detail-title': 'Oppgavedetaljer',\n  'task-info-dialog-title': '{{title}} Detaljer',\n  'download-start-message': 'Lastet ned {{taskName}}',\n  'download-pause-message': 'Nedlasting av {{taskName}} er midlertidig stoppet',\n  'download-stop-message': 'Nedlastingen stoppet ikke {{taskName}}',\n  'download-error-message': 'Det oppstod en feil under nedlasting av {{taskName}}',\n  'download-complete-message': 'Nedlastingen er fullført {{taskName}}',\n  'download-complete-notify': 'Last ned fullført',\n  'bt-download-complete-message': 'Nedlastingen er fullført, {{taskName}}, seeding',\n  'bt-download-complete-notify': 'BT-nedlasting fullført, seeding...',\n  'bt-download-complete-tips': 'Tips: Du kan stoppe en oppgave for å avslutte seedingen',\n  'bt-stopping-seeding-tip': 'Når du stopper seedingen, vil det ta litt tid å koble fra, vent...',\n  'download-fail-message': 'Kunne ikke laste ned {{taskName}}',\n  'download-fail-notify': 'Nedlasting feilet'\n}\n"
  },
  {
    "path": "src/shared/locales/nb/window.js",
    "content": "export default {\n  'reload': 'Last inn på nytt',\n  'close': 'Lukk',\n  'minimize': 'Minimer',\n  'zoom': 'Forstør',\n  'toggle-fullscreen': 'Gå til fullskjerm',\n  'front': 'Send alle til forgrunnen'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/about.js",
    "content": "export default {\n  'engine-version': 'Engine Versie',\n  'license': 'Licentie',\n  'about': 'Over',\n  'release': 'Versie',\n  'support': 'Ondersteuning'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/app.js",
    "content": "export default {\n  'task-list': 'Taken',\n  'add-task': 'Taak toevoegen',\n  'about': 'Over Motrix',\n  'preferences': 'Voorkeuren...',\n  'check-for-updates': 'Naar updates zoeken...',\n  'check-updates-now': 'Jetzt prüfen',\n  'checking-for-updates': 'Updates zoeken ...',\n  'check-for-updates-title': 'Naar updates zoeken...',\n  'update-available-message': 'Er is een nieuwe versie van Motrix beschikbaar, nu updaten?',\n  'update-not-available-message': 'U heeft de laatste versie!',\n  'update-downloaded-message': 'Klaar om te installeren...',\n  'update-error-message': 'Update fout',\n  'engine-damaged-message': 'De engine is beschadigd, opnieuw installeren a.u.b. :(',\n  'engine-missing-message': 'De engine mist, opnieuw installeren a.u.b. :(',\n  'system-error-title': 'Systeemfout',\n  'system-error-message': 'Applicatie kon niet worden gestart: {{message}}',\n  'hide': 'Motrix verbergen',\n  'hide-others': 'Verberg anderen',\n  'unhide': 'Laat alles zien',\n  'show': 'Laat Motrix zien',\n  'quit': 'Motrix afsluiten',\n  'under-development-message': 'Sorry, deze functie is nog in ontwikkeling...',\n  'yes': 'Ja',\n  'no': 'Nee',\n  'save': 'Opslaan',\n  'reset': 'Resetten',\n  'cancel': 'Annuleren',\n  'submit': 'Verzenden',\n  'gt1d': '> 1 dag',\n  'hour': 'u',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/edit.js",
    "content": "export default {\n  'undo': 'Ongedaan maken',\n  'redo': 'Opnieuw doen',\n  'cut': 'Knipplen',\n  'copy': 'Kopiëren',\n  'paste': 'Plakken',\n  'delete': 'Verwijderen',\n  'select-all': 'Alles selecteren'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/help.js",
    "content": "export default {\n  'official-website': 'Motrix Website',\n  'manual': 'Handleiding',\n  'release-notes': 'Versie informatie...',\n  'report-problem': 'Probleem melden',\n  'toggle-dev-tools': 'Ontwikkelaarstools aan/uit-schakelen'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/nl/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Bestand',\n  'task': 'Taak',\n  'edit': 'Bewerken',\n  'window': 'Venster',\n  'help': 'Help'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/preferences.js",
    "content": "export default {\n  'basic': 'Basis',\n  'advanced': 'Geavanceerd',\n  'lab': 'Experimenteel',\n  'save': 'Opslaan & Toepassen',\n  'save-success-message': 'Instellingen succesvol opgeslagen',\n  'save-fail-message': 'Instellingen opslaan mislukt',\n  'discard': 'Weggooien',\n  'startup': 'Opstarten',\n  'open-at-login': 'Bij login openen',\n  'keep-window-state': 'Behoud grootte en positie van venster bij afsluiten',\n  'auto-resume-all': 'Automatisch alle niet voltooide taken hervatten',\n  'default-dir': 'Standaard map',\n  'mas-default-dir-tips': 'Door de sandbox permissie restricties van de App Store wordt aangeraden om ~/Downloads als standaard downloadmap in te stellen',\n  'transfer-settings': 'Tranmissie',\n  'transfer-speed-upload': 'Upload limiet',\n  'transfer-speed-download': 'Download limiet',\n  'transfer-speed-unlimited': 'Onbeperkt',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Magnetlink opslaan als torrent bestand',\n  'bt-auto-download-content': 'Automatisch magnet en torrent content downloaden',\n  'bt-force-encryption': 'BT dwngwyrddysgu gorfodol',\n  'keep-seeding': 'Blijf seeden tot het het handmatig wordt gestopt',\n  'seed-ratio': 'Seed ratio',\n  'seed-time': 'Starttijd',\n  'seed-time-unit': 'Protocol',\n  'task-manage': 'Taakbeheer',\n  'max-concurrent-downloads': 'Maximaal actieve taken',\n  'max-connection-per-server': 'Maximale verbindingen per server',\n  'new-task-show-downloading': 'Automatisch downloaden tonen na toevoegen taak',\n  'no-confirm-before-delete-task': 'Geen bevestiging nodig voor verwijderen taak',\n  'continue': 'Verder gaan',\n  'task-completed-notify': 'Notificatie wanneer download klaar is',\n  'auto-purge-record': 'Automatisch wissen van downloadrecords bij verlaten van app',\n  'ui': 'UI',\n  'appearance': 'Uiterlijk',\n  'theme-auto': 'Automatisch',\n  'theme-light': 'Licht',\n  'theme-dark': 'Donker',\n  'auto-hide-window': 'Venster automatisch verbergen',\n  'run-mode': 'Uitvoeren als',\n  'run-mode-standard': 'Standaard uitvoering',\n  'run-mode-tray': 'Toepassing voor systeemvak',\n  'run-mode-hide-tray': 'Toepassing in systeemvak verbergen',\n  'tray-speedometer': 'Menubalk toon real-time snelheid',\n  'show-progress-bar': 'Downloadvoortgangsbalk weergeven',\n  'language': 'Taal',\n  'change-language': 'Taal veranderen',\n  'hide-app-menu': 'App menu verbergen (alleen Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Proxy aanzetten',\n  'proxy-bypass-input-tips': 'Proxy instellingen omzeilen voor deze hosts en domeinen (één per regel)',\n  'proxy-scope-download': 'Downloaden',\n  'proxy-scope-update-app': 'Toepassing bijwerken',\n  'proxy-scope-update-trackers': 'Trackers bijwerken',\n  'proxy-tips': 'Proxy handleiding bekijken',\n  'bt-tracker': 'Tracker Servers',\n  'bt-tracker-input-tips': 'Tracker Servers, één per regel',\n  'bt-tracker-tips': 'Aanbevolen:',\n  'sync-tracker-tips': 'Synchronisieren',\n  'auto-sync-tracker': 'Update de tracker lijst elke dag automatisch',\n  'port': 'Luister poort',\n  'bt-port': 'BT luister poort',\n  'dht-port': 'DHT luister poort',\n  'security': 'Beveiliging',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC Luisterpoort',\n  'rpc-secret': 'RPC geheim',\n  'rpc-secret-tips': 'bekijk RPC geheim handleiding',\n  'developer': 'Ontwikkelaar',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'User-Agent nabootsen',\n  'aria2-conf-path': 'Ingebed pad voor aria2.conf',\n  'app-log-path': 'Applicatie log pad',\n  'download-session-path': 'Downloadsessie pad',\n  'session-reset': 'Reset download sessie',\n  'session-reset-confirm': 'Weet u zeker dat u de downloadsessie instellingen wilt resetten?',\n  'factory-reset': 'Fabrieksinstellingen',\n  'factory-reset-confirm': 'Weet u zeker dat u terug wilt gaan naar fabrieksinstellingen?',\n  'lab-warning': '⚠️ Het activeren van experimentele functies kan zorgen voor onstabiliteit of dataverlies. Kies op eigen risico!',\n  'download-protocol': 'Protocollen',\n  'protocols-default-client': 'Als Standardclient voor de volgende protocollen instellen',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Donner [ thunder:// ]',\n  'browser-extensions': 'Browserextensies',\n  'baidu-exporter': 'Baidu Exporter',\n  'browser-extensions-tips': 'Beschikbaar gesteld door de community',\n  'baidu-exporter-help': 'Klik hier voor hulp',\n  'auto-update': 'Auto-Update',\n  'auto-check-update': 'Automatische naar updates zoeken',\n  'last-check-update-time': 'Laatste keer gecontroleerd op updates'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/subnav.js",
    "content": "export default {\n  'task-list': 'Taken',\n  'preferences': 'Voorkeuren'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/task.js",
    "content": "export default {\n  'active': 'Actief',\n  'waiting': 'Wachten',\n  'stopped': 'Gestopt',\n  'new-task': 'Nieuwe taak',\n  'new-bt-task': 'Nieuwe BT taak',\n  'open-file': 'Torrentbestand openen...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Een taak URL per regel (ondersteunt magnet)',\n  'thunder-link-tips': 'Tip: Thunder links zijn mogelijk niet downloadbaar na decodering',\n  'new-task-uris-required': 'Geef a.u.b. tenminste een geldige URL',\n  'new-task-torrent-required': 'Kies a.u.b. een torrentbestand',\n  'file-name': 'Bestandsnaam',\n  'file-extension': 'Bestandstype',\n  'file-size': 'Bestandsgrootte',\n  'file-completed-size': 'Gedownload',\n  'selected-files-sum': 'Geselecteerd: {{selectedFilesCount}} bestanden, totale grootte {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Kies a.u.b. ten minste 1 bestand',\n  'task-gid': 'GID',\n  'task-name': 'Taak naam',\n  'task-out': 'Hernoemen',\n  'task-out-tips': 'Optioneel',\n  'task-split': 'Splits',\n  'task-dir': 'Map',\n  'pause-task': 'Taak pauzeren',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorisatie',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Error',\n  'task-piece': 'Deel',\n  'task-piece-length': 'Deelgrootte',\n  'task-num-pieces': 'Delen',\n  'task-bittorrent-info': 'Torrent Info',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Aanmaakdatum',\n  'task-bittorrent-comment': 'Opmerking',\n  'task-progress-info': 'Voortgang',\n  'task-status': 'Status',\n  'task-num-seeders': 'Seerders',\n  'task-connections': 'Verbindingen',\n  'task-file-size': 'Grootte',\n  'task-download-speed': 'Downloadsnelheid',\n  'task-upload-speed': 'Uploadsnelheid',\n  'task-download-length': 'Gedownload',\n  'task-upload-length': 'Geupload',\n  'task-ratio': 'Verhouding',\n  'task-peer-host': 'Host',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Client',\n  'navigate-to-downloading': 'Navigeer naar downloaden',\n  'show-advanced-options': 'Geavanceerde opties',\n  'copyright-warning': 'Copyright waarschuwing',\n  'copyright-warning-message': 'Het bestand dat u wilt te downloaden kan onder het auteursrecht vallen. Controleer of u in het bezit bent van de benodigde licentie',\n  'copyright-yes': 'Ja, ik heb toestemming',\n  'copyright-no': 'Nee, ik heb geen toestemming',\n  'copyright-error-message': 'Kon taak niet toevoegen vanwege copyright probleem',\n  'pause-task-success': 'Taak \"{{taskName}}\" succesvol gepauzeerd',\n  'pause-task-fail': 'Taak \"{{taskName}}\" pauzeren mislukt',\n  'resume-task': 'Taak hervatten',\n  'resume-task-success': 'Taak \"{{taskName}}\" succesvol hervat',\n  'resume-task-fail': 'Taak \"{{taskName}}\" hervatten mislukt',\n  'delete-task': 'Taak verwijderen',\n  'delete-selected-tasks': 'Geselecteerde taken verwijderen',\n  'delete-task-confirm': 'Weet u zeker dat u de download van \"{{taskName}}\" wilt verwijderen?',\n  'batch-delete-task-confirm': 'Weet u zeker dat u {{count}} taken in 1 keer verwijderen?',\n  'delete-task-label': 'Bestand ook verwijderen',\n  'delete-task-success': 'Taak \"{{taskName}}\" succesvol verwijderd',\n  'delete-task-fail': 'Taak \"{{taskName}}\" verwijderen mislukt',\n  'remove-task-file-fail': 'Bestand verwijderen mislukt, gelieve handmatig verwijderen',\n  'remove-task-config-file-fail': 'Taakconfiguratiebestand verwijderen mislukt, gelieve handmatig verwijderenn',\n  'move-task-up': 'Taak naar boven verplaatsen',\n  'move-task-down': 'Taak naar beneden verplaatsen',\n  'pause-all-task': 'Alle taken pauzeren',\n  'pause-all-task-success': 'Alle taken succesvol gepauzeerd',\n  'pause-all-task-fail': 'De taken konden niet worden gepauzeerd',\n  'resume-all-task': 'Alle taken hervatten',\n  'resume-all-task-success': 'Alle taken succesvol hervat',\n  'resume-all-task-fail': 'Taken hervatten mislukt',\n  'select-all-task': 'Selecteer alle taken',\n  'clear-recent-tasks': 'Verwijder recente taken',\n  'purge-record': 'Taakrecord verwijderen',\n  'purge-record-success': 'Taakrecord succesvol verwijderd',\n  'purge-record-fail': 'Taakrecord kon niet worden verwijderd',\n  'batch-delete-task-success': 'Taken succesvol in batch verwijderd',\n  'batch-delete-task-fail': 'Taken in batch konden niet worden verwijderd',\n  'refresh-list': 'Takenlijst verversen',\n  'no-task': 'Geen taken actief',\n  'copy-link': 'Link kopiëren',\n  'copy-link-success': 'Link succesvol gekopieerd',\n  'remove-record': 'Verwijder taak record',\n  'remove-record-confirm': 'Weet u zeker dat u het downloadrecord wilt verwijderen voor \"{{taskName}}\"?',\n  'remove-record-label': 'Bestand ook verwijderen',\n  'remove-record-success': 'Taakrecord voor \"{{taskName}}\" succesvol verwijderd',\n  'remove-record-fail': 'Taakrecord voor \"{{taskName}}\" kon niet verwijderd worden',\n  'show-in-folder': 'Taak in map bekijken',\n  'file-not-exist': 'Doelbestand bestaad niet of is verwijderd',\n  'file-path-error': 'Bestandpad fout',\n  'opening-task-message': '\"{{taskName}}\" openen...',\n  'get-task-name': 'Taak naam ophalen...',\n  'remaining-prefix': 'Resterend',\n  'select-torrent': 'Sleep torrentbestand hier naartoe of klik om te selecteren',\n  'task-info-dialog-title': '{{title}} Details',\n  'download-start-message': 'Start downloaden van {{taskName}}',\n  'download-pause-message': 'Pauzeeer downloaden van {{taskName}}',\n  'download-stop-message': 'Download van{{taskName}} gestopt',\n  'download-error-message': 'Fout bij downloaden van {{taskName}} opgetreden',\n  'download-complete-message': 'Download van {{taskName}} geannuleerd',\n  'download-complete-notify': 'Download geannuleerd',\n  'bt-download-complete-message': 'Download van {{taskName}} voltooid, seeden...',\n  'bt-download-complete-notify': 'BT Download voltooid, seeden...',\n  'bt-download-complete-tips': 'Tip: U kunt een taak stoppen om het seeden de beëindigen',\n  'bt-stopping-seeding-tip': 'Seeden stoppen. Het duurt even voor de verbindingen zijn verbroken, even geduld...',\n  'download-fail-message': 'Download van {{taskName}} mislukt',\n  'download-fail-notify': 'Download mislukt'\n}\n"
  },
  {
    "path": "src/shared/locales/nl/window.js",
    "content": "export default {\n  'reload': 'Vervesen',\n  'close': 'Sluiten',\n  'minimize': 'Minimaliseren',\n  'zoom': 'Zoomen',\n  'toggle-fullscreen': 'Volledig scherm tonen',\n  'front': 'Alles naar voorgrond brengen'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/about.js",
    "content": "export default {\n  'engine-version': 'Wersja silnika',\n  'license': 'Licencja',\n  'about': 'O programie',\n  'release': 'Wydania',\n  'support': 'Wsparcie'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/app.js",
    "content": "export default {\n  'task-list': 'Zadania',\n  'add-task': 'Dodaj zadanie',\n  'about': 'O Motrix',\n  'preferences': 'Ustawienia...',\n  'check-for-updates': 'Sprawdź aktualizacje...',\n  'check-updates-now': 'Sprawdź teraz',\n  'checking-for-updates': 'Sprawdzanie aktualizacji ...',\n  'check-for-updates-title': 'Sprawdź aktualizację',\n  'update-available-message': 'Dostępna jest nowa wersja Motrix, czy chcesz zaktualizować program?',\n  'update-not-available-message': 'Masz zainstalowaną najnowszą wersję!',\n  'update-downloaded-message': 'Gotowe do instalacji...',\n  'update-error-message': 'Błąd aktualizacji',\n  'engine-damaged-message': 'Silnik został uszkodzony, spróbuj zreinstalować program : (',\n  'engine-missing-message': 'Nie można znaleźć silnika, spróbuj zreinstalować program : (',\n  'system-error-title': 'Błąd systemu',\n  'system-error-message': 'Aplikacja nie mogła zostać uruchomiona: {{message}}',\n  'hide': 'Schowaj Motrix',\n  'hide-others': 'Schowaj pozostałe',\n  'unhide': 'Pokaż wszystkie',\n  'show': 'Pokaż Motrix',\n  'quit': 'Opuść Motrix',\n  'under-development-message': 'Przepraszamy ale ta funkcja nie jest jeszcze gotowa...',\n  'yes': 'Tak',\n  'no': 'Nie',\n  'save': 'Zapisać',\n  'reset': 'Odrzucać',\n  'cancel': 'Anuluj',\n  'submit': 'Dodaj',\n  'gt1d': '> 1 dzień',\n  'hour': 'g',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/edit.js",
    "content": "export default {\n  'undo': 'Cofnij',\n  'redo': 'Ponów',\n  'cut': 'Wytnij',\n  'copy': 'Skopiuj',\n  'paste': 'Wklej',\n  'delete': 'Usuń',\n  'select-all': 'Wybierz wszystkie '\n}\n"
  },
  {
    "path": "src/shared/locales/pl/help.js",
    "content": "export default {\n  'official-website': 'Oficjalna strona internetowa',\n  'manual': 'Instrukcja',\n  'release-notes': 'Dziennik wydań...',\n  'report-problem': 'Zgłoś problem',\n  'toggle-dev-tools': 'Włącz narzędzia dla developerów'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/pl/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Plik',\n  'task': 'Zadanie',\n  'edit': 'Edycja',\n  'window': 'Okno',\n  'help': 'Pomoc'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/preferences.js",
    "content": "export default {\n  'basic': 'Podstawowe',\n  'advanced': 'Zaawansowane',\n  'lab': 'Laboratorium',\n  'save': 'Zapisz i zastosuj',\n  'save-success-message': 'Pomyślnie zapisano ustawienia',\n  'save-fail-message': 'Nie udało się zapisać ustawień',\n  'discard': 'Odrzuć',\n  'startup': 'Startup',\n  'open-at-login': 'Uruchom przy logowaniu',\n  'keep-window-state': 'Zachowaj wielkość i pozycję okna podczas wychodzenia',\n  'auto-resume-all': 'Automatycznie uruchom wszystkie zatrzymane zadania',\n  'default-dir': 'Domyślna ścieżka',\n  'mas-default-dir-tips': 'Z powodu restrykcji dostępu spowodowanych trybem piaskownicy w App Store, domyślną rekomendawaną lokalizacją pobrań jest ~/Downloads',\n  'transfer-settings': 'Przesyłanie',\n  'transfer-speed-upload': 'Limit prędkości przesyłania',\n  'transfer-speed-download': 'Limit prędkości pobierania',\n  'transfer-speed-unlimited': 'Nieograniczona',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Zapisz link magnetyczny jako plik torrent',\n  'bt-auto-download-content': 'Automatycznie pobieraj zawartość magnesu i torrenta',\n  'bt-force-encryption': 'Obrigar a criptografia forçada do BT',\n  'keep-seeding': 'Kontynuuj wysiew aż do zatrzymania go ręcznie',\n  'seed-ratio': 'Stosunek nasion',\n  'seed-time': 'Czas siewu',\n  'seed-time-unit': 'minuty',\n  'task-manage': 'Zarządzania zadaniami',\n  'max-concurrent-downloads': 'Maksymalna liczba aktywnych zadań',\n  'max-connection-per-server': 'Maksymalna liczba połączeń na serwer',\n  'new-task-show-downloading': 'Automatycznie pokaż pobieranie po dodaniu nowego zadania',\n  'no-confirm-before-delete-task': 'Usunięcie zadania nie będzie wymagać potwierdzenia',\n  'continue': 'Kontynuuj',\n  'task-completed-notify': 'Powiadomnie po ukończeniu zadania',\n  'auto-purge-record': 'Automatycznie usuń pobrane zadania po zamknięciu aplikacji',\n  'ui': 'UI',\n  'appearance': 'Wygląd',\n  'theme-auto': 'Auto',\n  'theme-light': 'Jasny',\n  'theme-dark': 'Ciemny',\n  'auto-hide-window': 'Automatycznie chowaj okno',\n  'run-mode': 'Uruchom jako',\n  'run-mode-standard': 'Zwykła aplikacja',\n  'run-mode-tray': 'Aplikacja z paska powiadomień',\n  'run-mode-hide-tray': 'Ukryj aplikację zasobnika',\n  'tray-speedometer': 'Pasek zadań będzie pokazywać aktualną prędkość',\n  'show-progress-bar': 'Pokaż pasek postępu pobierania',\n  'language': 'Język',\n  'change-language': 'Zmień język',\n  'hide-app-menu': 'Schowaj menu aplikacji (Tylko Windows i Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Włącz Proxy',\n  'proxy-bypass-input-tips': 'Omiń ustawiania proxy dla tych hostów i domen, jeden na linię',\n  'proxy-scope-download': 'Pobierz',\n  'proxy-scope-update-app': 'Aktualizacja aplikacji',\n  'proxy-scope-update-trackers': 'Aktualizacja trackera',\n  'proxy-tips': 'Pokaż instrukcję proxy',\n  'bt-tracker': 'Trackery',\n  'bt-tracker-input-tips': 'Trackery, jeden na linię',\n  'bt-tracker-tips': 'Zalecane: ',\n  'sync-tracker-tips': 'Synchronizowane',\n  'auto-sync-tracker': 'Codziennie aktualizuj listę trackerów',\n  'port': 'Nasłuchujące porty',\n  'bt-port': 'Torrent nasłuchujący port',\n  'dht-port': 'DHT nasłuchujący port',\n  'security': 'Bezpieczeństwo',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Port nasłuchu RPC',\n  'rpc-secret': 'Sekret RPC',\n  'rpc-secret-tips': 'Pokaż instrukcję sekretu RPC',\n  'developer': 'Developer',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Udawaj user-agent\\'a',\n  'aria2-conf-path': 'Wbudowana ścieżka aria2.conf',\n  'app-log-path': 'Ścieżka logów',\n  'download-session-path': 'Ścieżka sesji pobranych',\n  'factory-reset': 'Przywróć domyślne ustawiania',\n  'factory-reset-confirm': 'Jesteś pewnien że chcesz przywrócić domyślne ustawienia',\n  'lab-warning': '⚠️ Włączenie laboratorium może doprowadzić do błędów i utraty danych, włącz na własne ryzyko!',\n  'download-protocol': 'Protokoły',\n  'protocols-default-client': 'Ustaw jako domyślny klient dla tych protokołów',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Dodatki',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Dostarczone przez społeczność, ',\n  'baidu-exporter-help': 'Naciśnij tutaj po pomoc',\n  'auto-update': 'Aktualizuj automatycznie',\n  'auto-check-update': 'Automatycznie sprawdzaj aktualizacje',\n  'last-check-update-time': 'Ostatni raz kiedy sprawdzono aktualizację',\n  'not-saved': 'Preferencje nie zostały zapisane',\n  'not-saved-confirm': 'Zmienione preferencje zostaną utracone, czy na pewno chcesz wyjść?'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/subnav.js",
    "content": "export default {\n  'task-list': 'Zadania',\n  'preferences': 'Ustawienia'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/task.js",
    "content": "export default {\n  'active': 'Pobieranie',\n  'waiting': 'Wstrzymane',\n  'stopped': 'Zatrzymane',\n  'new-task': 'Nowe zadanie',\n  'new-bt-task': 'Nowe zadanie Torrent',\n  'open-file': 'Otwórz plik torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Jeden link na linię (wspiera magnet)',\n  'thunder-link-tips': 'Porada: Linki Thunder mogą nie być możliwe do pobrania po zdekodowaniu',\n  'new-task-uris-required': 'Proszę wprowadzić przynajmniej jeden poprawny URL',\n  'new-task-torrent-required': 'Proszę wybrać plik torrent',\n  'file-name': 'Nazwa',\n  'file-extension': 'Rozbudowa',\n  'file-size': 'Rozmiar',\n  'file-completed-size': 'Pobrano',\n  'selected-files-sum': 'Wybrano: {{selectedFilesCount}} plików, Wspólny rozmiar {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Wybierz co najmniej jeden plik',\n  'task-gid': 'GID',\n  'task-name': 'Nazwa zadania',\n  'task-out': 'Zmień nazwę',\n  'task-out-tips': 'Opcjonalne',\n  'task-split': 'Części',\n  'task-dir': 'Zapisz jako',\n  'pause-task': 'Zapauzuj',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autoryzacja',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Błąd',\n  'task-piece': 'Kawałek',\n  'task-piece-length': 'Rozmiar elementu',\n  'task-num-pieces': 'Kawałki',\n  'task-bittorrent-info': 'Informacje o torrentach',\n  'task-info-hash': 'Haszysz',\n  'task-bittorrent-creation-date': 'Data utworzenia',\n  'task-bittorrent-comment': 'Komentarz',\n  'task-progress-info': 'Postęp',\n  'task-status': 'Status',\n  'task-num-seeders': 'Siewniki',\n  'task-connections': 'Znajomości',\n  'task-file-size': 'Rozmiar',\n  'task-download-speed': 'Prędkość pobierania',\n  'task-upload-speed': 'Prędkość wysyłania',\n  'task-download-length': 'Pobrano',\n  'task-upload-length': 'Przesłane',\n  'task-ratio': 'Stosunek',\n  'task-peer-host': 'Gospodarz',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Klient',\n  'navigate-to-downloading': 'Automatycznie uruchom pobieranie',\n  'show-advanced-options': 'Opcje zaawansowane',\n  'copyright-warning': 'Ostrzeżenie dt. praw autorskich',\n  'copyright-warning-message': 'Plik który chcesz pobrać może być chroniony prawami autorskimi, proszę upewnij się że masz prawo go pobierać/używać',\n  'copyright-yes': 'Tak, mam prawo',\n  'copyright-no': 'Nie, nie mam prawa',\n  'copyright-error-message': 'Nie można było dodać zadania z powodu praw autorskich',\n  'pause-task-success': 'Pomyślnie zapauzowano \"{{taskName}}\"',\n  'pause-task-fail': 'Nie udało się zapauzować \"{{taskName}}\"',\n  'resume-task': 'Wznów zadanie',\n  'resume-task-success': 'Pomyślnie wznowiono \"{{taskName}}\"',\n  'resume-task-fail': 'Nie udało się wznowić \"{{taskName}}\"',\n  'delete-task': 'Usuń zadanie',\n  'delete-selected-tasks': 'Usuń wybrane zadania',\n  'delete-task-confirm': 'Czy jesteś pewien że chcesz usunąć zadanie \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Czy jesteś pewnien że chcesz usunąć {{count}} zadań?',\n  'delete-task-label': 'Usuń z plikami',\n  'delete-task-success': 'Pomyślnie usunięto \"{{taskName}}\"',\n  'delete-task-fail': 'Nie udało sie usunąć \"{{taskName}}\"',\n  'remove-task-file-fail': 'Nie udało się usunąć plików pochodzących z zadań, proszę, usuń je manualnie.',\n  'remove-task-config-file-fail': 'Nie udało się usunąć pliku konfiguracyjnego zadanie, proszę, usuń je manualnie',\n  'move-task-up': 'Przenieś zadanie do góry',\n  'move-task-down': 'Przenieś zadanie do dołu',\n  'pause-all-task': 'Wstrzymaj wszystkie zadania',\n  'pause-all-task-success': 'Pomyślnie wstrzymano wszystkie zadania',\n  'pause-all-task-fail': 'Nie udało się wstrzymać wszystkich zadań',\n  'resume-all-task': 'Wznów wszystkie zadania',\n  'resume-all-task-success': 'Pomyślnie wznowiono wszystkie zadania',\n  'resume-all-task-fail': 'Nie udało się wznowić wszystkich zadań',\n  'select-all-task': 'Wybierz wszystkie zadania',\n  'clear-recent-tasks': 'Wyczyść ostatnie zadania',\n  'purge-record': 'Usuń wszystkie rekordy zadań',\n  'purge-record-success': 'Pomyślnie usunięto wszystkie rekordy zadań',\n  'purge-record-fail': 'Nie udało się usunąć wszystkich rekordów zadań',\n  'batch-delete-task-success': 'Pomyślnie usunięto wiele zadań',\n  'batch-delete-task-fail': 'Nie udało się usunąć wielu zadań',\n  'refresh-list': 'Odśwież listę zadań',\n  'no-task': 'Nie ma obecnie żadnych zadań',\n  'copy-link': 'Skopiuj link',\n  'copy-link-success': 'Pomyślnie skopiowano link',\n  'remove-record': 'Usuń rekord zadania',\n  'remove-record-confirm': 'Czy jesteś pewien że chcesz usunąć rekord dla zadania \"{{taskName}}\"?',\n  'remove-record-label': 'Usuń z plikami',\n  'remove-record-success': 'Pomyślnie usunięto rekord dla zadania \"{{taskName}}\"',\n  'remove-record-fail': 'Nie udało się usunąć rekordu dla zadania \"{{taskName}}\"',\n  'show-in-folder': 'Pokaż zadania w folderze',\n  'file-not-exist': 'Docelowe pliki nie istnieją lub zostały usunięte',\n  'file-path-error': 'Błąd ścieżki pliku',\n  'opening-task-message': 'Otiweranie \"{{taskName}}\" ...',\n  'get-task-name': 'Pobieranie nazwy pliku...',\n  'remaining-prefix': 'Pozostało',\n  'select-torrent': 'Przenieś tutaj plik torrent, lub naciśnij aby wybrać',\n  'task-info-dialog-title': 'Szczegóły {{title}}',\n  'download-start-message': 'Rozpoczęto pobieranie {{taskName}}',\n  'download-pause-message': 'Wstrzymano pobieranie {{taskName}}',\n  'download-stop-message': 'Zatrzymano pobieranie {{taskName}}',\n  'download-error-message': 'Wystąpił błąd podczas pobierania {{taskName}}',\n  'download-complete-message': 'Ukończono pobieranie {{taskName}}',\n  'download-complete-notify': 'Pobieranie ukończone',\n  'bt-download-complete-message': 'Ukończono pobieranie {{taskName}}, seedowanie',\n  'bt-download-complete-notify': 'Ukończono pobieranie pliku torrent, seedowanie...',\n  'bt-download-complete-tips': 'Wskazówka: Możesz zatrzymań zadanie aby wyłączyć seedowanie',\n  'bt-stopping-seeding-tip': 'Zatrzymywane seedowania, zajmie chwilę rozłączanie się od klientów, proszę czekać...',\n  'download-fail-message': 'Nie udało się pobrać {{taskName}}',\n  'download-fail-notify': 'Nie udało się pobrać'\n}\n"
  },
  {
    "path": "src/shared/locales/pl/window.js",
    "content": "export default {\n  'reload': 'Przeładuj',\n  'close': 'Zamknij',\n  'minimize': 'Minimalizuj',\n  'zoom': 'Powiększ',\n  'toggle-fullscreen': 'Włącz tryb pełnoekranowy',\n  'front': 'Przenieś wszystko na przód'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/about.js",
    "content": "export default {\n  'engine-version': 'Versão da Engine',\n  'license': 'Licença',\n  'about': 'Sobre',\n  'release': 'Release',\n  'support': 'Suporte'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/app.js",
    "content": "export default {\n  'task-list': 'Tarefas',\n  'add-task': 'Adicionar Tarefa',\n  'about': 'Sobre o Motrix',\n  'preferences': 'Preferências...',\n  'check-for-updates': 'Verificar por Atualizações...',\n  'check-updates-now': 'Verificar agora',\n  'checking-for-updates': 'Verificar atualizações ...',\n  'check-for-updates-title': 'Verificar por Atualizações',\n  'update-available-message': 'Uma nova versão do Motrix está disponível, atualize agora?',\n  'update-not-available-message': 'Você esta atualizado!',\n  'update-downloaded-message': 'Pronto para instalar...',\n  'update-error-message': 'Atualizar erro',\n  'engine-damaged-message': 'O motor está danificado, por favor, reinstale : (',\n  'engine-missing-message': 'O motor está faltando, por favor, reinstale : (',\n  'system-error-title': 'Erro do sistema',\n  'system-error-message': 'O aplicativo falhou ao iniciar: {{message}}',\n  'hide': 'Ocultar Motrix',\n  'hide-others': 'Ocultar Outros',\n  'unhide': 'Exibir Todos',\n  'show': 'Exibir Motrix',\n  'quit': 'Sair do Motrix',\n  'under-development-message': 'Desculpe, essa funcionalidade está em desenvolvimento...',\n  'yes': 'Sim',\n  'no': 'Não',\n  'save': 'Salvar',\n  'reset': 'Descartar',\n  'cancel': 'Cancelar',\n  'submit': 'Enviar',\n  'gt1d': '> 1 dia',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/edit.js",
    "content": "export default {\n  'undo': 'Desfazer',\n  'redo': 'Refazer',\n  'cut': 'Recortar',\n  'copy': 'Copiar',\n  'paste': 'Copiar',\n  'delete': 'Apagar',\n  'select-all': 'Selecionar Tudo'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/help.js",
    "content": "export default {\n  'official-website': 'Motrix Website',\n  'manual': 'Manual',\n  'release-notes': 'Notas de Lançamento...',\n  'report-problem': 'Reportar um Problema',\n  'toggle-dev-tools': 'Alternar Developer Tools'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Arquivo',\n  'task': 'Tarefa',\n  'edit': 'Editar',\n  'window': 'Janela',\n  'help': 'Ajuda'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/preferences.js",
    "content": "export default {\n  'basic': 'Básico',\n  'advanced': 'Avançado',\n  'lab': 'Lab',\n  'save': 'Salvar & Aplicar',\n  'save-success-message': 'Salvar preferências com sucesso',\n  'save-fail-message': 'Salvar preferências falhadas',\n  'discard': 'Descartar',\n  'startup': 'Inicialização',\n  'open-at-login': 'Abra no login',\n  'keep-window-state': 'Restaurar tamanho e posição da janela',\n  'auto-resume-all': 'Auto resumir todas as tarefas não finalizadas',\n  'default-dir': 'Diretório Padrão',\n  'mas-default-dir-tips': 'Devido às restrições de permissões do sandbox da App Store, recomenda-se que o diretório de download padrão seja definido como o diretório de downloads',\n  'transfer-settings': 'Transmissão',\n  'transfer-speed-upload': 'limite de envio',\n  'transfer-speed-download': 'limite de transferência',\n  'transfer-speed-unlimited': 'Ilimitado',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Salvar link magnético como arquivo torrent',\n  'bt-auto-download-content': 'Baixar automaticamente ímã e conteúdo de torrent',\n  'bt-force-encryption': 'Forçar criptografia BT',\n  'keep-seeding': 'Continue semeando até pará-lo manualmente',\n  'seed-ratio': 'Proporção de sementes',\n  'seed-time': 'Hora da Semente',\n  'seed-time-unit': 'minutos',\n  'task-manage': 'Gerenciador de Tarefas',\n  'max-concurrent-downloads': 'Máximo de tarefas ativas',\n  'max-connection-per-server': 'Máximo de coneções por servidor',\n  'new-task-show-downloading': 'Auto exibir progresso depois de adicionar uma tarefa',\n  'no-confirm-before-delete-task': 'Nenhuma confirmação é necessária antes de excluir a tarefa',\n  'continue': 'Continuar',\n  'task-completed-notify': 'Notificação após o download ser completado',\n  'auto-purge-record': 'Auto remover registro de download quando o app for finalizado',\n  'ui': 'UI',\n  'appearance': 'Aparência',\n  'theme-auto': 'Automático',\n  'theme-light': 'Clara',\n  'theme-dark': 'Escura',\n  'auto-auto-hide-window': 'Ocultar janelas automaticamente',\n  'run-mode': 'Correr como',\n  'run-mode-standard': 'Aplicação padrão',\n  'run-mode-tray': 'Aplicativo de bandeja',\n  'run-mode-hide-tray': 'Ocultar aplicativo da bandeja',\n  'tray-speedometer': 'A bandeja da barra de menus mostra a velocidade em tempo real',\n  'show-progress-bar': 'Mostrar barra de progresso de download',\n  'language': 'Idioma',\n  'change-language': 'Mudar o Idioma',\n  'hide-app-menu': 'Ocultar o Menu do App (Windows & Linux apenas)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Habilitar Proxy',\n  'proxy-bypass-input-tips': 'Ignorar configurações de proxy para esses hosts e domínios, um por linha',\n  'proxy-scope-download': 'Download',\n  'proxy-scope-update-app': 'Atualizar Aplicativo',\n  'proxy-scope-update-trackers': 'Atualizar rastreadores',\n  'proxy-tips': 'Exibir manual do proxy',\n  'bt-tracker': 'Rastreadores',\n  'bt-tracker-input-tips': 'servidor rastreador, um por linha',\n  'bt-tracker-tips': 'Recomendar:',\n  'sync-tracker-tips': 'Sincronizar',\n  'auto-sync-tracker': 'Atualize a lista de rastreadores todos os dias automaticamente',\n  'port': 'Portas de escuta',\n  'bt-port': 'Porta de escuta BT',\n  'dht-port': 'Porta de escuta DHT',\n  'security': 'Segurança',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Porta de Escuta RPC',\n  'rpc-secret': 'Segredo de RPC',\n  'rpc-secret-tips': 'Veja o manual secreto de RPC',\n  'developer': 'Desenvolverdor',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Caminho de aria2.conf incorporado',\n  'app-log-path': 'Diretório de logs',\n  'download-session-path': 'Diretório da sessão de Downloads',\n  'factory-reset': 'Configurações de Fábrica',\n  'factory-reset-confirm': 'Você tem certeza de que deseja resetar às configurações de fábrica?',\n  'lab-warning': '⚠️ Habilitar os recursos de lab pode causar perda de dados e fechar o app inesperadamete. Use por sua conta e risco!',\n  'download-protocol': 'Protocolo',\n  'protocols-default-client': 'Definir como cliente padrão para os seguintes protocolos',\n  'protocols-magnet': 'Magnético [ magnet:// ]',\n  'protocols-thunder': 'Trovão [ thunder:// ]',\n  'browser-extensions': 'Extensões',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Fornecido pela comunidade, ',\n  'baidu-exporter-help': 'Clique aqui ver as instruções de uso',\n  'auto-update': 'Atualização automática',\n  'auto-check-update': 'A verificação automática de atualizações',\n  'last-check-update-time': 'última verificação do tempo de atualização',\n  'not-saved': 'Preferências não salvas',\n  'not-saved-confirm': 'As preferências modificadas serão perdidas. Tem certeza de que deseja sair?'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/subnav.js",
    "content": "export default {\n  'task-list': 'Tarefas',\n  'preferences': 'Preferências'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/task.js",
    "content": "export default {\n  'active': 'Baixando',\n  'waiting': 'Aguardando',\n  'stopped': 'Parou',\n  'new-task': 'Nova Tarefa',\n  'new-bt-task': 'Nova Tarefa BT',\n  'open-file': 'Abra o arquivo Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Uma url por linha (magnet links são aceitos)',\n  'thunder-link-tips': 'Dica: Thunder links não podem ser baixados após decodificados',\n  'new-task-uris-required': 'Por favor, insira pelo menos um URL de recurso válido',\n  'new-task-torrent-required': 'Por favor, selecione um arquivo torrent',\n  'file-name': 'Nome',\n  'file-extension': 'Ext',\n  'file-size': 'Tamanho',\n  'file-completed-size': 'Baixado',\n  'selected-files-sum': 'Selecionado: {{selectedFilesCount}} arquivos, total {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Selecione pelo menos um arquivo',\n  'task-gid': 'GID',\n  'task-name': 'Nome da Tarefa',\n  'task-out': 'Renomear',\n  'task-out-tips': 'Opcional',\n  'task-split': 'Splits',\n  'task-dir': 'Diretório',\n  'pause-task': 'Pausar Tarefa',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorização',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Erro',\n  'task-piece': 'Artigo',\n  'task-piece-length': 'Tamanho da peça',\n  'task-num-pieces': 'Peças',\n  'task-bittorrent-info': 'Torrent Info',\n  'task-info-hash': 'Cerquilha',\n  'task-bittorrent-creation-date': 'Data de criação',\n  'task-bittorrent-comment': 'Comente',\n  'task-progress-info': 'Progresso',\n  'task-status': 'Status',\n  'task-num-seeders': 'Semeadores',\n  'task-connections': 'Conexões',\n  'task-file-size': 'Tamanho',\n  'task-download-speed': 'Velocidade de download',\n  'task-upload-speed': 'Velocidade de upload',\n  'task-download-length': 'Baixado',\n  'task-upload-length': 'Carregado',\n  'task-ratio': 'Razão',\n  'task-peer-host': 'Hospedeiro',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Cliente',\n  'navigate-to-downloading': 'Navegar para o Downloading',\n  'show-advanced-options': 'Opções Avançadas',\n  'copyright-warning': 'Aviso de Copyright',\n  'copyright-warning-message': 'O arquivo que você deseja baixa pode ser protegido por direitos de copyright de áudio ou vídeo, tenha certeza que você possui os direitos de licensa.',\n  'copyright-yes': 'Sim, Eu Tenho',\n  'copyright-no': 'Não',\n  'copyright-error-message': 'A operação falhou devido os direitos de copyright',\n  'pause-task-success': 'Tarefa \"{{taskName}}\" pausada com sucesso',\n  'pause-task-fail': 'Falha ao pausar a tarefa \"{{taskName}}\"',\n  'resume-task': 'Resumir Tarefa',\n  'resume-task-success': 'Tarefa \"{{taskName}}\" resumida com sucesso',\n  'resume-task-fail': 'Falha ao resumir a tarefa \"{{taskName}}\"',\n  'delete-task': 'Apagar Tarefa',\n  'delete-selected-tasks': 'Apagar as Tarefas Selecionadas',\n  'delete-task-confirm': 'Você tem certeza de que deseja apagar a seguinte tarefa de download: \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Tem certeza de que deseja remover {{count}} tarefas de download em lote?',\n  'delete-task-label': 'Apagar com os Arquivos',\n  'delete-task-success': 'Tarefa \"{{taskName}}\" apagada com sucesso',\n  'delete-task-fail': 'Falha ao apagar a tarefa \"{{taskName}}\"',\n  'remove-task-file-fail': 'Falha ao tentar deletar o arquivo da tarefa, por favor, apague manualmente',\n  'remove-task-config-file-fail': 'Falha ao tentar deletar o arquivo de configuração da tarefa, por favor, apague manualmente',\n  'move-task-up': 'Mover Tarefa para Cima',\n  'move-task-down': 'Mover Tarefa para Baixar',\n  'pause-all-task': 'Pausar Todas as Tarefas',\n  'pause-all-task-success': 'Todas as tarefas foram pausadas com sucesso',\n  'pause-all-task-fail': 'Falha ao pausar todas as tarefas',\n  'resume-all-task': 'Resumir todas as tarefas',\n  'resume-all-task-success': 'Todas as tarefas foram resumidas com sucesso',\n  'resume-all-task-fail': 'Falha ao resumir todas as tarefas',\n  'select-all-task': 'Selecione todas as tarefas',\n  'clear-recent-tasks': 'Limpar todas as tarefas',\n  'purge-record': 'Expurgar o Registro de Tarefas',\n  'purge-record-success': 'O registro de tarefas foi expurgado com sucesso',\n  'purge-record-fail': 'Falha ao expurgar o registro de tarefas',\n  'batch-delete-task-success': 'Exclua com êxito as tarefas em lote',\n  'batch-delete-task-fail': 'Falha ao excluir tarefas no lote',\n  'refresh-list': 'Atualizar a Lista de Tarefas',\n  'no-task': 'Não há downloads no momento',\n  'copy-link': 'Copiar Link',\n  'copy-link-success': 'Link copiado com sucesso',\n  'remove-record': 'Apagar Registro de Tarefas',\n  'remove-record-confirm': 'Você tem certeza de que deseja remover o registro de download da tarefa \"{{taskName}}\"?',\n  'remove-record-label': 'Deletar com arquivos',\n  'remove-record-success': 'Registro da tarefa \"{{taskName}}\" apagado com sucesso',\n  'remove-record-fail': 'Falha ao apagar o registro da tarefa \"{{taskName}}\"',\n  'show-in-folder': 'Exibir Tarefa na Pasta',\n  'file-not-exist': 'O arquivo não existe ou foi apagado',\n  'file-path-error': 'Erro no caminho do arquivo',\n  'opening-task-message': 'Abrindo \"{{taskName}}\" ...',\n  'get-task-name': 'Obter o nome da tarefa...',\n  'remaining-prefix': 'Restante',\n  'select-torrent': 'Arraste o torrent aqui, ou clique para selecionar',\n  'task-info-dialog-title': '{{title}} Detalhes',\n  'download-start-message': 'Iniciar download {{taskName}}',\n  'download-pause-message': 'Pausar download {{taskName}}',\n  'download-stop-message': '{{taskName}} download parado',\n  'download-error-message': '{{taskName}} ocorreu um erro no download',\n  'download-complete-message': '{{taskName}} download completado',\n  'download-complete-notify': 'Download Completado',\n  'bt-download-complete-message': '{{taskName}} download completado, propagação ...',\n  'bt-download-complete-notify': 'Download do BT concluído, Propagação ...',\n  'bt-download-complete-tips': 'Dicas: você pode parar a tarefa para terminar a propagação',\n  'bt-stopping-seeding-tip': 'Parando a propagação, levará algum tempo para desconectar, aguarde ...',\n  'download-fail-message': '{{taskName}} falha no download',\n  'download-fail-notify': 'Falha no Download'\n}\n"
  },
  {
    "path": "src/shared/locales/pt-BR/window.js",
    "content": "export default {\n  'reload': 'Recarregar',\n  'close': 'Fechar',\n  'minimize': 'Minimizar',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Modo Full Screen',\n  'front': 'Trazer Tudo Para a Frente'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/about.js",
    "content": "export default {\n  'engine-version': 'Versiune Motor',\n  'license': 'Licență',\n  'about': 'Despre',\n  'release': 'Versiuni',\n  'support': 'Ajutor'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/app.js",
    "content": "export default {\n  'task-list': 'Sarcini',\n  'add-task': 'Adaugă sarcină',\n  'about': 'Despre Motrix',\n  'preferences': 'Setări...',\n  'check-for-updates': 'Verifică actualizări...',\n  'check-updates-now': 'Verifică acum',\n  'checking-for-updates': 'Verific actualizări ...',\n  'check-for-updates-title': 'Verifică actualizări',\n  'update-available-message': 'O nouă versiune Motrix este disponibilă, actualizaţi acum?',\n  'update-not-available-message': 'Versiunea este la zi!',\n  'update-downloaded-message': 'Gata de instalare...',\n  'update-error-message': 'Eroare actualizare',\n  'engine-damaged-message': 'Motorul este deteriorat, vă rugăm să reinstalați : (',\n  'engine-missing-message': 'Motorul lipseşte, vă rugăm să reinstalați : (',\n  'system-error-title': 'Eroare sistem',\n  'system-error-message': 'Aplicaţia nu a putut porni: {{message}}',\n  'hide': 'Ascunde Motrix',\n  'hide-others': 'Ascunde celelalte',\n  'unhide': 'Arată toate',\n  'show': 'Arată Motrix',\n  'quit': 'Închide Motrix',\n  'under-development-message': 'Scuze, această caracteristică este în curs de dezvoltare...',\n  'yes': 'Da',\n  'no': 'Nu',\n  'save': 'Salvați',\n  'reset': 'Aruncați',\n  'cancel': 'Anuleaza',\n  'submit': 'Trimite',\n  'gt1d': '> 1 zi',\n  'hour': 'h',\n  'minute': 'm',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/edit.js",
    "content": "export default {\n  'undo': 'Undo',\n  'redo': 'Redo',\n  'cut': 'Taie',\n  'copy': 'Copiază',\n  'paste': 'Lipeşte',\n  'delete': 'Şterge',\n  'select-all': 'Selectează tot'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/help.js",
    "content": "export default {\n  'official-website': 'Website Motrix',\n  'manual': 'Manual',\n  'release-notes': 'Notele versiunii...',\n  'report-problem': 'Raportează problemă',\n  'toggle-dev-tools': 'Deschide unelete dezvoltator'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ro/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'File',\n  'task': 'Sarcină',\n  'edit': 'Edit',\n  'window': 'Fereastra',\n  'help': 'Ajutor'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/preferences.js",
    "content": "export default {\n  'basic': 'De bază',\n  'advanced': 'Avansate',\n  'lab': 'Laborator',\n  'save': 'Salvează şi Aplică',\n  'save-success-message': 'Setările au fost salvate cu succes',\n  'save-fail-message': 'Nu s-au putut salva setările',\n  'discard': 'Renunţă',\n  'startup': 'Pornire',\n  'open-at-login': 'Deschide la logare',\n  'keep-window-state': 'Păstrați dimensiunea și poziția ferestrei la închiderea aplicaţiei',\n  'auto-resume-all': 'Reia automat toate sarcinile neterminate',\n  'default-dir': 'Calea implicită',\n  'mas-default-dir-tips': 'Datorită restricțiilor privind permisiunea sandbox-ului din App Store, se recomandă ca directorul de descărcare implicit să fie setat la ~/Downloads',\n  'transfer-settings': 'Transmitere',\n  'transfer-speed-upload': 'Limită upload',\n  'transfer-speed-download': 'Limită download',\n  'transfer-speed-unlimited': 'Nelimitat',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Salvați linkul magnet ca fișier torrent',\n  'bt-auto-download-content': 'Descărcați automat magnetul și conținutul torrent',\n  'bt-force-encryption': 'Criptare forţată BT',\n  'keep-seeding': 'Păstrați însămânțarea până când o opriți manual',\n  'seed-ratio': 'Raportul semințelor',\n  'seed-time': 'Timpul semințelor',\n  'seed-time-unit': 'minute',\n  'task-manage': 'Managementul sarcinilor',\n  'max-concurrent-downloads': 'Sarcini active maxime',\n  'max-connection-per-server': 'Conexiuni maxime per server',\n  'new-task-show-downloading': 'Afișează automat descărcarea după adăugarea sarcinii',\n  'no-confirm-before-delete-task': 'Nu este necesară confirmarea înainte de ștergerea sarcinii',\n  'continue': 'Continuă',\n  'task-completed-notify': 'Notifică după finalizarea sarcinii',\n  'auto-purge-record': 'Ștergeți automat înregistrările de descărcare la ieșirea din aplicație',\n  'ui': 'Interfață Utilizator',\n  'appearance': 'Aspect',\n  'theme-auto': 'Automat',\n  'theme-light': 'Temă luminoasă',\n  'theme-dark': 'Temă întunecată',\n  'auto-hide-window': 'Ascunde automat fereastra',\n  'run-mode': 'Ruleaza ca',\n  'run-mode-standard': 'Aplicatie standard',\n  'run-mode-tray': 'Aplicație pentru tava',\n  'run-mode-hide-tray': 'Ascundeți aplicația tăvii',\n  'tray-speedometer': 'Iconița din bară arată viteza în timp real',\n  'show-progress-bar': 'Afișează bara de progres a descărcării',\n  'language': 'Limba',\n  'change-language': 'Schimbă limba',\n  'hide-app-menu': 'Ascundeți meniul aplicației (numai Windows și Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Activați Proxy',\n  'proxy-bypass-input-tips': 'Ignorați setările proxy pentru aceste gazde și domenii, câte una pe fiecare linie',\n  'proxy-scope-download': 'Descărcare',\n  'proxy-scope-update-app': 'Actualizare aplicație',\n  'proxy-scope-update-trackers': 'Actualizare Trackere',\n  'proxy-tips': 'Vizualizați manualul proxy',\n  'bt-tracker': 'Severe tracker (Torrent Tracker)',\n  'bt-tracker-input-tips': 'Severe tracker, câte unul pe fiecare linie',\n  'bt-tracker-tips': 'Recomandat: ',\n  'sync-tracker-tips': 'Sincronizare',\n  'auto-sync-tracker': 'Actualizați automat lista de servere tracker în fiecare zi',\n  'port': 'Porturi pe care se ascultă',\n  'bt-port': 'Port ascultare BT',\n  'dht-port': 'Port ascultare DHT',\n  'security': 'Securitate',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Portul de Ascultare RPC',\n  'rpc-secret': 'Secret RPC',\n  'rpc-secret-tips': 'Vizualizați manualul pentru secret RPC',\n  'developer': 'Dezvoltator',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Calea aria2.conf încorporată',\n  'app-log-path': 'Calea jurnalului aplicației',\n  'download-session-path': 'Calea sesiunii de download',\n  'factory-reset': 'Resetare la setări din fabrică',\n  'factory-reset-confirm': 'Sigur doriți să reveniți la setările din fabrică?',\n  'lab-warning': '⚠️ Activarea funcțiilor de laborator poate duce la blocarea aplicației sau pierderea datelor, contiunați pe propriul risc!',\n  'download-protocol': 'Protocoale',\n  'protocols-default-client': 'Setați client implicit pentru următoarele protocoale',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Extensii',\n  'baidu-exporter': 'Exportator Baidu',\n  'browser-extensions-tips': 'Furnizat de comunitate, ',\n  'baidu-exporter-help': 'Faceți clic aici pentru instrucțiuni',\n  'auto-update': 'Actualizare automată',\n  'auto-check-update': 'Verificați automat dacă sunt disponibile actualizări',\n  'last-check-update-time': 'Ultima dată când au fost verificate actualizările disponibile',\n  'not-saved': 'Preferințele nu au fost salvate',\n  'not-saved-confirm': 'Preferințele modificate se vor pierde, sunteți sigur că plecați?'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/subnav.js",
    "content": "export default {\n  'task-list': 'Sarcini',\n  'preferences': 'Setări'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/task.js",
    "content": "export default {\n  'active': 'Se descarcă',\n  'waiting': 'În aşteptare',\n  'stopped': 'Oprite',\n  'new-task': 'Sarcină Nouă',\n  'new-bt-task': 'Sarcină Nouă BT',\n  'open-file': 'Deschide fişier Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'O adresă URL de sarcină pe linie (acceptă magnet)',\n  'thunder-link-tips': 'Sfat: Este posibil ca linkurile Thunder să nu poată fi descărcate după decodare',\n  'new-task-uris-required': 'Introduceți cel puțin o adresă URL validă',\n  'new-task-torrent-required': 'Vă rugăm să selectați un fișier torrent',\n  'file-name': 'Nume',\n  'file-extension': 'Extensie',\n  'file-size': 'Dimensiune',\n  'file-completed-size': 'Descărcat',\n  'selected-files-sum': 'Selectate: {{selectedFilesCount}} fișiere, dimensiune totală {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Vă rugăm să selectați cel puțin un fișier',\n  'task-gid': 'GID',\n  'task-name': 'Nume sarcină',\n  'task-out': 'Redenumeşte',\n  'task-out-tips': 'Opțional',\n  'task-split': 'Scindări',\n  'task-dir': 'Salvează în',\n  'pause-task': 'Întrerupeți sarcina',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Autorizare',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Eroare',\n  'task-piece': 'Bucată',\n  'task-piece-length': 'Dimensiunea piesei',\n  'task-num-pieces': 'Piese',\n  'task-bittorrent-info': 'Informații despre torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Data crearii',\n  'task-bittorrent-comment': 'cometariu',\n  'task-progress-info': 'Progres',\n  'task-status': 'stare',\n  'task-num-seeders': 'Semănători',\n  'task-connections': 'Conexiuni',\n  'task-file-size': 'mărimea',\n  'task-download-speed': 'Viteza de descărcare',\n  'task-upload-speed': 'Viteza de upload',\n  'task-download-length': 'Descărcat',\n  'task-upload-length': 'Încărcat',\n  'task-ratio': 'Raport',\n  'task-peer-host': 'Gazdă',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Client',\n  'navigate-to-downloading': 'Mergi la Descărcări',\n  'show-advanced-options': 'Opțiuni avansate',\n  'copyright-warning': 'Avertisment privind drepturile de autor',\n  'copyright-warning-message': 'Fișierul pe care doriți să îl descărcați poate fi audio sau video cu drepturi de autor, vă rugăm să vă asigurați că aveți permisiunea pentru a-l accesa.',\n  'copyright-yes': 'Da, am permisiunea',\n  'copyright-no': 'Nu, nu am permisiunea',\n  'copyright-error-message': 'Adăugarea sarcinii nu a reușit din cauza problemei drepturilor de autor',\n  'pause-task-success': 'Sarcina \"{{taskName}}\" a fost întreruptă cu succes',\n  'pause-task-fail': 'Nu s-a putut întrerupe sarcina \"{{taskName}}\"',\n  'resume-task': 'Reluați sarcina',\n  'resume-task-success': 'Sarcina \"{{taskName}}\" a fost reluata cu succes',\n  'resume-task-fail': 'Nu s-a putut relua sarcina \"{{taskName}}\"',\n  'delete-task': 'Șterge sarcina',\n  'delete-selected-tasks': 'Șterge sarcinile selectate',\n  'delete-task-confirm': 'Sunteţi sigur că vreţi să eliminați sarcina \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Sigur doriți să eliminați {{count}} sarcini de descărcare în lot?',\n  'delete-task-label': 'Ștergeți cu Fișiere',\n  'delete-task-success': 'Sarcina \"{{taskName}}\" a fost ștearsă cu succes',\n  'delete-task-fail': 'Nu s-a putut ștearge sarcina \"{{taskName}}\"',\n  'remove-task-file-fail': 'Nu s-au putut șterge fișierele sarcinii, ștergeți-le manual',\n  'remove-task-config-file-fail': 'Ștergerea fișierului de configurare a sarcinii nu a reușit, ștergeți-l manual',\n  'move-task-up': 'Muta sarcina în sus',\n  'move-task-down': 'Muta sarcina în jos',\n  'pause-all-task': 'Întrerupeți toate sarcinile',\n  'pause-all-task-success': 'Toate sarcinile au fost întrerupte cu succes',\n  'pause-all-task-fail': 'Nu s-au putut întrerupe toate sarcinile',\n  'resume-all-task': 'Reia toate sarcinile',\n  'resume-all-task-success': 'S-au reluat cu succes toate sarcinile',\n  'resume-all-task-fail': 'Nu s-au putut relua toate sarcinile',\n  'select-all-task': 'Selectează Toate sarcinile',\n  'clear-recent-tasks': 'Șterge sarcinile recente',\n  'purge-record': 'Șterge istoricul sarcinilor',\n  'purge-record-success': 'Istoricul sarcinilor a fost șters',\n  'purge-record-fail': 'Nu s-a putut șterge istoricul sarcinilor',\n  'batch-delete-task-success': 'Sarcinile în lot au fost șterse cu succes',\n  'batch-delete-task-fail': 'Nu s-au putut șterge sarcinile in lot',\n  'refresh-list': 'Reîncarcă lista de sarcini',\n  'no-task': 'Nu există sarcini curente',\n  'copy-link': 'Copiază Link',\n  'copy-link-success': 'Link copiat cu succes',\n  'remove-record': 'Șterge istoric sarcină',\n  'remove-record-confirm': 'Sigur doriți să ștergeți istoricul sarcinii \"{{taskName}}\"?',\n  'remove-record-label': 'Ștergeți cu Fișiere',\n  'remove-record-success': 'Istoricul sarcinii a fost șters cu succes pentru \"{{taskName}}\"',\n  'remove-record-fail': 'Nu s-au putut șterge istoricul sarcinii pentru \"{{taskName}}\"',\n  'show-in-folder': 'Arată sarcina în Folder',\n  'file-not-exist': 'Fișierul țintă nu există sau a fost șters',\n  'file-path-error': 'Eroare cale fișier',\n  'opening-task-message': 'Deschid \"{{taskName}}\" ...',\n  'get-task-name': 'Se obține numele sarcinii...',\n  'remaining-prefix': 'Mai rămân',\n  'select-torrent': 'Trageți fișierul torrent aici sau faceți clic pentru a selecta',\n  'task-info-dialog-title': '{{title}} Detalii',\n  'download-start-message': 'Descărcarea a început {{taskName}}',\n  'download-pause-message': 'Descărcare întreruptă {{taskName}}',\n  'download-stop-message': 'Descărcare oprită {{taskName}}',\n  'download-error-message': 'A apărut o eroare la descărcare {{taskName}}',\n  'download-complete-message': 'Descărcare finalizată {{taskName}}',\n  'download-complete-notify': 'Descărcare finalizată',\n  'bt-download-complete-message': 'Descărcare finalizată {{taskName}}, se stă la seed',\n  'bt-download-complete-notify': 'Descărcare BT finalizată, se stă la seed...',\n  'bt-download-complete-tips': 'Sfaturi: puteți opri o sarcină pentru a nu mai sta la \"seed\"',\n  'bt-stopping-seeding-tip': 'Se oprește statul la seed, va dura ceva timp pentru a vă deconecta, vă rugăm să așteptați ...',\n  'download-fail-message': 'Descărcarea nu a reușit {{taskName}}',\n  'download-fail-notify': 'Descărcarea nu a reușit'\n}\n"
  },
  {
    "path": "src/shared/locales/ro/window.js",
    "content": "export default {\n  'reload': 'Reîncărcă',\n  'close': 'Închide',\n  'minimize': 'Minimizează',\n  'zoom': 'Zoom',\n  'toggle-fullscreen': 'Intră în mod ecran complet',\n  'front': 'Adu-le pe toate în față'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/about.js",
    "content": "export default {\n  'engine-version': 'Версия движка',\n  'license': 'Лицензия',\n  'about': 'Информация',\n  'release': 'Релиз',\n  'support': 'Поддержка'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/app.js",
    "content": "export default {\n  'task-list': 'Задачи',\n  'add-task': 'Добавить задание',\n  'about': 'О Motrix',\n  'preferences': 'Настройки...',\n  'check-for-updates': 'Проверить обновление...',\n  'check-updates-now': 'Проверить сейчас',\n  'checking-for-updates': 'Проверка обновлений ...',\n  'check-for-updates-title': 'Проверить обновления',\n  'update-available-message': 'Новая версия Motrix доступна для скачивания, скачать сейчас?',\n  'update-not-available-message': 'Вы уже используете самую последнюю версию!',\n  'update-downloaded-message': 'Готово к установке...',\n  'update-error-message': 'Ошибка обновления',\n  'engine-damaged-message': 'Движок поврежден. Пожалуйста, переустановите его : (',\n  'engine-missing-message': 'Движок потерян. Пожалуйста, переустановите его : (',\n  'system-error-title': 'Системная ошибка',\n  'system-error-message': 'Ошибка запуска приложения: {{message}}',\n  'hide': 'Спрятать Motrix',\n  'hide-others': 'Спрятать все остальное',\n  'unhide': 'Отобразить все',\n  'show': 'Отобразить Motrix',\n  'quit': 'Закрыть Motrix',\n  'under-development-message': 'К сожалению, эта функция все еще в разработке...',\n  'yes': 'Да',\n  'no': 'Нет',\n  'save': 'Сохранить',\n  'reset': 'Отмена',\n  'cancel': 'Отмена',\n  'submit': 'Подтвердить',\n  'gt1d': '> 1 день',\n  'hour': 'ч',\n  'minute': 'м',\n  'second': 'с'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/edit.js",
    "content": "export default {\n  'undo': 'Отмена',\n  'redo': 'Повторить',\n  'cut': 'Вырезать',\n  'copy': 'Копировать',\n  'paste': 'Вставить',\n  'delete': 'Удалить',\n  'select-all': 'Выбрать все'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/help.js",
    "content": "export default {\n  'official-website': 'Сайт Motrix',\n  'manual': 'Инструкция',\n  'release-notes': 'Пометки к релизу...',\n  'report-problem': 'Сообщить о проблеме',\n  'toggle-dev-tools': 'Переключить инструменты разработчика'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/ru/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Файл',\n  'task': 'Задания',\n  'edit': 'Редактировать',\n  'window': 'Окно',\n  'help': 'Помощь'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/preferences.js",
    "content": "export default {\n  'basic': 'Основные',\n  'advanced': 'Расширенные',\n  'lab': 'Лаборатория',\n  'save': 'Сохранить и применить',\n  'save-success-message': 'Настройки сохранены успешно',\n  'save-fail-message': 'Ошибка при сохранении настроек',\n  'discard': 'Отмена',\n  'startup': 'Запускать',\n  'open-at-login': 'Запускать програму вместе со стартом операционной системы',\n  'keep-window-state': 'Во время закрытия приложения, сохранять размер и положение окна',\n  'auto-resume-all': 'Автоматически возобновлять все незавершенные задачи',\n  'default-dir': 'Папка по умолчанию',\n  'mas-default-dir-tips': 'Из-за ограничения в App Store, рекомендуется устанавливать путь по умолчанию как ~/Downloads',\n  'transfer-settings': 'коробка передач',\n  'transfer-speed-upload': 'Лимит отдачи',\n  'transfer-speed-download': 'Лимит загрузки',\n  'transfer-speed-unlimited': 'Безлимитно',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Сохранить магнитную ссылку как торрент-файл',\n  'bt-auto-download-content': 'Автоматически загружать магнит и торрент',\n  'bt-force-encryption': 'Принудительное шифрование BT',\n  'keep-seeding': 'Продолжайте посев, пока не остановите его вручную',\n  'seed-ratio': 'Соотношение семян',\n  'seed-time': 'Время посева',\n  'seed-time-unit': 'минут',\n  'task-manage': 'Менеджер задач',\n  'max-concurrent-downloads': 'Максимум активных задач',\n  'max-connection-per-server': 'Максимум соединений на сервер',\n  'new-task-show-downloading': 'Автоматически отображать задачу после ее добавления',\n  'no-confirm-before-delete-task': 'Перед удалением задачи подтверждение не требуется',\n  'continue': 'Продолжить',\n  'task-completed-notify': 'Сообщение после окончания загрузки',\n  'auto-purge-record': 'Автоматически чистить записи о загрузках после закрытия приложения',\n  'ui': 'UI',\n  'appearance': 'Внешний вид',\n  'theme-auto': 'Автоматически',\n  'theme-light': 'Светлый',\n  'theme-dark': 'Темный',\n  'auto-hide-window': 'Автоскрытие окон',\n  'run-mode': 'Запускать как...',\n  'run-mode-standard': 'Стандартное приложение',\n  'run-mode-tray': 'Приложение в трее',\n  'run-mode-hide-tray': 'Скрыть приложение в трее',\n  'tray-speedometer': 'Панель меню отображает скорость в реальном времени',\n  'show-progress-bar': 'Показать индикатор загрузки',\n  'language': 'Язык',\n  'change-language': 'Сменить язык',\n  'hide-app-menu': 'Скрыть меню приложения (только для Windows и Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Использовать Proxy',\n  'proxy-bypass-input-tips': 'Обойти настройки прокси для этих хостов и доменов, по одному в строке',\n  'proxy-scope-download': 'Скачать',\n  'proxy-scope-update-app': 'Обновить приложение',\n  'proxy-scope-update-trackers': 'Обновить трекеры',\n  'proxy-tips': 'Посмотреть руководство по прокси',\n  'bt-tracker': 'Tracker Сервер',\n  'bt-tracker-input-tips': 'Tracker сервера, один в строку',\n  'bt-tracker-tips': 'Рекомендовано: ',\n  'sync-tracker-tips': 'Синхронизация',\n  'auto-sync-tracker': 'Обновлять список трекеров каждый день автоматически',\n  'port': 'Порты прослушивания',\n  'bt-port': 'Порт прослушивания BT',\n  'dht-port': 'Порт прослушивания DHT',\n  'security': 'Безопастность',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Порт прослушивания RPC',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Смотреть инструкцию RPC Secret',\n  'developer': 'Разработчик',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Макет User-Agent',\n  'aria2-conf-path': 'Встроенный путь к aria2.conf',\n  'app-log-path': 'Путь к журналу приложения',\n  'download-session-path': 'Загрузить путь сессии',\n  'factory-reset': 'Настройки по умолчанию',\n  'factory-reset-confirm': 'Вы уверены, что хотите вернуться к настройкам по умолчанию?',\n  'lab-warning': '⚠️ Включение функций лаборатории может привести к сбоям приложения и потери данных. Вы действуете на свой страх и риск!',\n  'download-protocol': 'Протоколы',\n  'protocols-default-client': 'Установить как клиента по умолчанию для следующих протоколов',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Расширения',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Предоставляются сообществом, ',\n  'baidu-exporter-help': 'Нажмите здесь для использования',\n  'auto-update': 'Автоматическое обновление',\n  'auto-check-update': 'Автоматически проверять обновления',\n  'last-check-update-time': 'Последняя проверка на обновления прошла в',\n  'not-saved': 'Настройки не сохранены',\n  'not-saved-confirm': 'Измененные настройки будут потеряны, вы обязательно уйдете?'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/subnav.js",
    "content": "export default {\n  'task-list': 'Задачи',\n  'preferences': 'Настройки'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/task.js",
    "content": "export default {\n  'active': 'Загрузки',\n  'waiting': 'Ожидание',\n  'stopped': 'Остановлено',\n  'new-task': 'Новое задание',\n  'new-bt-task': 'Новое BT задание',\n  'open-file': 'Открыть Torrent файл...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Один URL-задания в строку (поддержка magnet)',\n  'thunder-link-tips': 'Совет: Ссылки типа Thunder могут не загружаться после декодированния',\n  'new-task-uris-required': 'Введите хотя бы один действительный URL-адрес ресурса',\n  'new-task-torrent-required': 'Пожалуйста, выберите torrent файл',\n  'file-name': 'Имя файла',\n  'file-extension': 'Тип файла',\n  'file-size': 'Размер',\n  'file-completed-size': 'Завершенный',\n  'selected-files-sum': 'Выбрано: {{selectedFilesCount}} файлов, общий размер {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Пожалуйста, выберите хотя бы один файл',\n  'task-gid': 'GID',\n  'task-name': 'Имя загрузки',\n  'task-out': 'Переименовать',\n  'task-out-tips': 'Необязательный',\n  'task-split': 'Разбить',\n  'task-dir': 'Сохранить как',\n  'pause-task': 'Приостановить задание',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Авторизация',\n  'task-referer': 'Реферал',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Ошибка',\n  'task-piece': 'Кусок',\n  'task-piece-length': 'Размер куска',\n  'task-num-pieces': 'Шт',\n  'task-bittorrent-info': 'Информация о торрентах',\n  'task-info-hash': 'Хеш',\n  'task-bittorrent-creation-date': 'Дата создания',\n  'task-bittorrent-comment': 'Комментарий',\n  'task-progress-info': 'Прогресс',\n  'task-status': 'Статус',\n  'task-num-seeders': 'Сеялки',\n  'task-connections': 'Подключения',\n  'task-file-size': 'Размер',\n  'task-download-speed': 'Скорость загрузки',\n  'task-upload-speed': 'Скорость загрузки',\n  'task-download-length': 'Скачано',\n  'task-upload-length': 'Загружено',\n  'task-ratio': 'Соотношение',\n  'task-peer-host': 'Хозяин',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Клиент',\n  'navigate-to-downloading': 'Перейти к загрузке',\n  'show-advanced-options': 'Расширенные опции',\n  'copyright-warning': 'Предупреждение об авторских правах',\n  'copyright-warning-message': 'Файл, который вы пытаетесь загрузить, имеет запись об авторских правах на видео или аудио контент, пожалуйста проверьте, имеете ли вы права на загрузку этого файла.',\n  'copyright-yes': 'Да, у меня есть права',\n  'copyright-no': 'Нет, у меня нет прав',\n  'copyright-error-message': 'Ошибка при добавлении задачи из-за проблем с авторскими правами',\n  'pause-task-success': 'Успешно остановлено задание \"{{taskName}}\"',\n  'pause-task-fail': 'Ошибка во время остановки задания \"{{taskName}}\"',\n  'resume-task': 'Возобновить задание',\n  'resume-task-success': 'Успешно возобновлено задание \"{{taskName}}\"',\n  'resume-task-fail': 'Ошибка во время возобновления задания \"{{taskName}}\"',\n  'delete-task': 'Удалить задание',\n  'delete-selected-tasks': 'Удалить выбранные задания',\n  'delete-task-confirm': 'Вы уверены, что хотите удалить задание \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Вы уверены, что хотите удалить {{count}} задач загрузки в пакетном режиме?',\n  'delete-task-label': 'Удалить вместе с файлами',\n  'delete-task-success': 'Успешно удалено задание \"{{taskName}}\"',\n  'delete-task-fail': 'Ошибка во время удаления задания \"{{taskName}}\"',\n  'remove-task-file-fail': 'Ошибка во время удаления файла(ов) задания. Пожалуйста, удалите его (их) самостоятельно',\n  'remove-task-config-file-fail': 'Ошибка при удалении файла конфигурации задания. Пожалуйста, удалите его самостоятельно',\n  'move-task-up': 'Переместить задание вверх',\n  'move-task-down': 'Переместить задание вниз',\n  'pause-all-task': 'Приостановить все задания',\n  'pause-all-task-success': 'Успешно приостановлены все задания',\n  'pause-all-task-fail': 'Ошибка во время остановки всех заданий',\n  'resume-all-task': 'Возобновить все задания',\n  'resume-all-task-success': 'Успешно возобновлены все задания',\n  'resume-all-task-fail': 'Ошибка во время возобновления всех заданий',\n  'select-all-task': 'Выберите все задачи',\n  'clear-recent-tasks': 'Очистить последние задания',\n  'purge-record': 'Очистить записи о заданиях',\n  'purge-record-success': 'Успешно очищены записи о заданиях',\n  'purge-record-fail': 'Ошибка при очистке записей о заданиях',\n  'batch-delete-task-success': 'Успешно удалить задачи в пакетном режиме',\n  'batch-delete-task-fail': 'Не удалось удалить задачи в пакетном режиме',\n  'refresh-list': 'Обновить список заданий',\n  'no-task': 'Нет текущих заданий',\n  'copy-link': 'Копировать ссылку',\n  'copy-link-success': 'Успешно скопированна ссылка',\n  'remove-record': 'Удалить запись про задание',\n  'remove-record-confirm': 'Вы уверены, что хотите удалить запись про задание \"{{taskName}}\"?',\n  'remove-record-label': 'Удалить вместе с файлами',\n  'remove-record-success': 'Успешно удалена запись про задание \"{{taskName}}\"',\n  'remove-record-fail': 'Ошибка при удалении записи про задание \"{{taskName}}\"',\n  'show-in-folder': 'Отобразить файлы заданий в папке',\n  'file-not-exist': 'Запрошенный файл не существует или был удален',\n  'file-path-error': 'Ошибка в пути к файлу',\n  'opening-task-message': 'Открытие \"{{taskName}}\" ...',\n  'get-task-name': 'Получить имя задания...',\n  'remaining-prefix': 'Осталось',\n  'select-torrent': 'Перетяните torrent файл сюда, или нажмите выбрать',\n  'task-info-dialog-title': '{{title}} Детали',\n  'download-start-message': 'Началась загрузка {{taskName}}',\n  'download-pause-message': 'Приостановить загрузку {{taskName}}',\n  'download-stop-message': 'Остановить загрузку {{taskName}}',\n  'download-error-message': 'Ошибка во время загрузки {{taskName}}',\n  'download-complete-message': 'Завершена загрузка {{taskName}}',\n  'download-complete-notify': 'Загрузка завершена',\n  'bt-download-complete-message': 'Завершена загрузка {{taskName}}, раздача',\n  'bt-download-complete-notify': 'BT Загрузка завершена, раздача...',\n  'bt-download-complete-tips': 'Совет: Вы можете остановить задачу, чтобы остановить раздачу',\n  'bt-stopping-seeding-tip': 'Остановка посева, потребуется некоторое время, чтобы отключиться, пожалуйста, подождите...',\n  'download-fail-message': 'Не удалось загрузить {{taskName}}',\n  'download-fail-notify': 'Ошибка загрузки'\n}\n"
  },
  {
    "path": "src/shared/locales/ru/window.js",
    "content": "export default {\n  'reload': 'Перезагрузить',\n  'close': 'Закрыть',\n  'minimize': 'Свернуть',\n  'zoom': 'Увеличение',\n  'toggle-fullscreen': 'Перейти в полноэкранный режим',\n  'front': 'Поверх всех окон'\n}\n"
  },
  {
    "path": "src/shared/locales/th/about.js",
    "content": "export default {\r\n  'engine-version': 'เวอร์ชั่นเอ็นจิ้น',\r\n  'license': 'ใบอนุญาต',\r\n  'about': 'เกี่ยวกับ',\r\n  'release': 'ปล่อยเวอร์ชั่น',\r\n  'support': 'การสนับสนุน'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/app.js",
    "content": "export default {\r\n  'task-list': 'งาน',\r\n  'add-task': 'เพิ่มงาน',\r\n  'about': 'เกี่ยวกับ Motrix',\r\n  'preferences': 'ปรับแต่ง...',\r\n  'check-for-updates': 'ตรวจสอบการอัพเดต...',\r\n  'check-updates-now': 'ตรวสอบตอนนี้',\r\n  'checking-for-updates': 'กำลังตรวจสอบการอัพเดท ...',\r\n  'check-for-updates-title': 'ตรวจสอบการอัพเดท',\r\n  'update-available-message': 'มีเวอร์ชั่นใหม่ให้พร้อมใช้งาน อัพเดทตอนี้หรือไม่?',\r\n  'update-not-available-message': 'คุณใช้เวอร์ชั่นปัจจุบันแล้ว',\r\n  'update-downloaded-message': 'พร้อมติดตั้ง...',\r\n  'update-error-message': 'อัพเดทล้มเหลว',\r\n  'engine-damaged-message': 'เอ็นจิ้นเกิดความเสียหาย กรุณาติดตั้งใหม่อีกครั้ง : (',\r\n  'engine-missing-message': 'ไม่พบเอ็นจิ้น กรุณาติดตั้งใหม่อีกครั้ง : (',\r\n  'system-error-title': 'ระบบผิดพลาด',\r\n  'system-error-message': 'การเริ่มต้นแอปพลิเคชันล้มเหลว: {{message}}',\r\n  'hide': 'ซ่อน Motrix',\r\n  'hide-others': 'ซ่อนอย่างอืน',\r\n  'unhide': 'แสดงทั้งหมด',\r\n  'show': 'แสดง Motrix',\r\n  'quit': 'ออก Motrix',\r\n  'under-development-message': 'ขออภัย ฟีเจอร์นี้อยู่ระหว่างการพัฒนา...',\r\n  'yes': 'ใช่',\r\n  'no': 'ไม่',\r\n  'save': 'บันทึก',\r\n  'reset': 'ทิ้ง',\r\n  'cancel': 'ยกเลิก',\r\n  'submit': 'ส่ง',\r\n  'gt1d': '> 1 วัน',\r\n  'hour': 'ชม.',\r\n  'minute': 'น.',\r\n  'second': 'วิ'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/edit.js",
    "content": "export default {\r\n  'undo': 'ไม่ทำ',\r\n  'redo': 'ทำซ้ำ',\r\n  'cut': 'ตัด',\r\n  'copy': 'คัดลอก',\r\n  'paste': 'วาง',\r\n  'delete': 'ลบทิ้ง',\r\n  'select-all': 'เลือกทั้งหมด'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/help.js",
    "content": "export default {\r\n  'official-website': 'เว็บไซต์ Motrix',\r\n  'manual': 'คู่มือ',\r\n  'release-notes': 'บันทึกประจำรุ่น...',\r\n  'report-problem': 'รายงานปัญหา',\r\n  'toggle-dev-tools': 'สลับไปยังเครื่องมือสำหรับนักพัฒนา'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/index.js",
    "content": "import about from './about'\r\nimport app from './app'\r\nimport edit from './edit'\r\nimport help from './help'\r\nimport menu from './menu'\r\nimport preferences from './preferences'\r\nimport subnav from './subnav'\r\nimport task from './task'\r\nimport window from './window'\r\n\r\nexport default {\r\n  about,\r\n  app,\r\n  edit,\r\n  help,\r\n  menu,\r\n  preferences,\r\n  subnav,\r\n  task,\r\n  window\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/menu.js",
    "content": "export default {\r\n  'app': 'Motrix',\r\n  'file': 'ไฟล์',\r\n  'task': 'งาน',\r\n  'edit': 'แก้ไข',\r\n  'window': 'หน้าต่าง',\r\n  'help': 'ช่วยเหลือ'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/preferences.js",
    "content": "export default {\r\n  'basic': 'พื้นฐาน',\r\n  'advanced': 'ขั้นสูง',\r\n  'lab': 'ห้องทดลอง',\r\n  'save': 'บันทึกและนำไปใช้',\r\n  'save-success-message': 'บันทึกการตั้งค่าเรียบร้อยแล้ว',\r\n  'save-fail-message': 'บันทึกการตั้งค่าไม่สำเร็จ',\r\n  'discard': 'ทิ้ง',\r\n  'startup': 'เริ่มขึ้น',\r\n  'open-at-login': 'เปิดเมื่อเข้าสู่ระบบ',\r\n  'keep-window-state': 'รักษาขนาดและตำแหน่งของหน้าต่างเมื่อออก',\r\n  'auto-resume-all': 'ทำงานที่ยังไม่เสร็จทั้งหมดต่อโดยอัตโนมัติ',\r\n  'default-dir': 'เส้นทางเริ่มต้น',\r\n  'mas-default-dir-tips': 'เนื่องจากข้อจำกัดการอนุญาตแซนด์บ็อกซ์ของ App Store แนะนำให้ตั้งค่าไดเร็กทอรีดาวน์โหลดเริ่มต้นเป็น ~/Downloads',\r\n  'transfer-settings': 'การส่งผ่าน',\r\n  'transfer-speed-upload': 'จำกัดการอัพโหลด',\r\n  'transfer-speed-download': 'จำกัดการดาวน์โหลด',\r\n  'transfer-speed-unlimited': 'ไม่จำกัด',\r\n  'bt-settings': 'BitTorrent',\r\n  'bt-save-metadata': 'บันทึกลิงก์แม่เหล็กเป็นไฟล์ทอร์เรนต์',\r\n  'bt-auto-download-content': 'ดาวน์โหลดเนื้อหาแม่เหล็กและทอร์เรนต์โดยอัตโนมัติ',\r\n  'bt-force-encryption': 'บังคับการเข้ารหัส BT',\r\n  'keep-seeding': 'เก็บ Seed ไว้จนกว่าจะหยุดเอง',\r\n  'seed-ratio': 'เวลาแบ่งปัน BT',\r\n  'seed-time': 'อัตราส่วนแบ่ง BT',\r\n  'seed-time-unit': 'นาที',\r\n  'task-manage': 'การจัดการงาน',\r\n  'max-concurrent-downloads': 'งานที่ใช้งานสูงสุด',\r\n  'max-connection-per-server': 'การเชื่อมต่อสูงสุดต่อเซิร์ฟเวอร์',\r\n  'new-task-show-downloading': 'แสดงการดาวน์โหลดโดยอัตโนมัติหลังจากเพิ่มงาน',\r\n  'no-confirm-before-delete-task': 'ไม่จำเป็นต้องมีการยืนยันก่อนที่จะลบงาน',\r\n  'continue': 'ต่อ',\r\n  'task-completed-notify': 'แจ้งเตือนหลังดาวน์โหลดเสร็จสิ้น',\r\n  'auto-purge-record': 'ล้างบันทึกการดาวน์โหลดโดยอัตโนมัติเมื่อออกจากแอป',\r\n  'ui': 'UI',\r\n  'appearance': 'รูปร่าง',\r\n  'theme-auto': 'อัตโนมัติ',\r\n  'theme-light': 'สว่าง',\r\n  'theme-dark': 'มืด',\r\n  'auto-hide-window': 'ซ่อนหน้าต่างอัตโนมัติ',\r\n  'run-mode': 'เรียกใช้ด้วย',\r\n  'run-mode-standard': 'แอปพลิเคชั่นปกติ',\r\n  'run-mode-tray': 'แถบแจ้งเตือนแอปพลิเคชัน',\r\n  'run-mode-hide-tray': 'ซ่อนแอปพลิเคชันถาด',\r\n  'tray-speedometer': 'ถาดแถบเมนูแสดงความเร็วแบบเรียลไทม์',\r\n  'show-progress-bar': 'แสดงแถบความคืบหน้าการดาวน์โหลด',\r\n  'language': 'ภาษา',\r\n  'change-language': 'เปลี่ยนภาษา',\r\n  'hide-app-menu': 'ซ่อนเมนูแอป (Windows & Linux เท่านั้น)',\r\n  'proxy': 'พร็อกซี่',\r\n  'enable-proxy': 'เปิดใช้งานพร็อกซี่',\r\n  'proxy-bypass-input-tips': 'ข้ามการตั้งค่าพร็อกซีสำหรับโฮสต์และโดเมนเหล่านี้ หนึ่งรายการต่อบรรทัด',\r\n  'proxy-scope-download': 'ดาวน์โหลด',\r\n  'proxy-scope-update-app': 'อัปเดตแอปพลิเคชัน',\r\n  'proxy-scope-update-trackers': 'อัปเดตแทร็กเกอร์',\r\n  'proxy-tips': 'ดูคู่มือพร็อกซี',\r\n  'bt-tracker': 'เซิร์ฟเวอร์ติดตาม',\r\n  'bt-tracker-input-tips': 'เซิร์ฟเวอร์ตัวติดตาม หนึ่งตัวต่อบรรทัด',\r\n  'bt-tracker-tips': 'ขอแนะนำ: ',\r\n  'sync-tracker-tips': 'ซิงค์',\r\n  'auto-sync-tracker': 'อัพเดทรายการตัวติดตามทุกวันโดยอัตโนมัติ',\r\n  'port': 'Listen Ports',\r\n  'bt-port': 'BT Listen Port',\r\n  'dht-port': 'DHT Listen Port',\r\n  'security': 'ความปลอดภัย',\r\n  'rpc': 'RPC',\r\n  'rpc-listen-port': 'พอร์ตฟัง RPC',\r\n  'rpc-secret': 'RPC Secret',\r\n  'rpc-secret-tips': 'ดูคู่มือลับ RPC',\r\n  'developer': 'นักพัฒนา',\r\n  'user-agent': 'User-Agent',\r\n  'mock-user-agent': 'จำลอง User-Agent',\r\n  'aria2-conf-path': 'เส้นทาง aria2.conf ที่ฝังอยู่',\r\n  'app-log-path': 'เส้นทางบันทึกแอป',\r\n  'download-session-path': 'เส้นทางดาวน์โหลดเซสชัน',\r\n  'session-reset': 'รีเซ็ตเซสชั่นการดาวน์โหลด',\r\n  'session-reset-confirm': 'คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตเซสชันการดาวน์โหลด',\r\n  'factory-reset': 'ตั้งค่าจากโรงงาน',\r\n  'factory-reset-confirm': 'คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนกลับเป็นการตั้งค่าจากโรงงาน',\r\n  'lab-warning': '⚠️ การเปิดใช้งานฟีเจอร์แล็บอาจส่งผลให้แอปขัดข้องหรือข้อมูลสูญหาย ตัดสินใจยอมรับความเสี่ยงเอง!',\r\n  'download-protocol': 'โปรโตคอล',\r\n  'protocols-default-client': 'ตั้งเป็นไคลเอนต์เริ่มต้นสำหรับโปรโตคอลต่อไปนี้',\r\n  'protocols-magnet': 'แม่เหล็ก [ magnet:// ]',\r\n  'protocols-thunder': 'Thunder [ thunder:// ]',\r\n  'browser-extensions': 'ส่วนขยาย',\r\n  'baidu-exporter': 'BaiduExporter',\r\n  'browser-extensions-tips': 'มอบให้โดยชุมชน, ',\r\n  'baidu-exporter-help': 'คลิกที่นี่เพื่อใช้งาน',\r\n  'auto-update': 'อัพเดทอัตโนมัติ',\r\n  'auto-check-update': 'ตรวจสอบการอัพเดทอัตโนมัติ',\r\n  'last-check-update-time': 'ตรวจสอบการอัพเดทครั้งล่าสุด'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/subnav.js",
    "content": "export default {\r\n  'task-list': 'งาน',\r\n  'preferences': 'ปรับแต่ง'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/task.js",
    "content": "export default {\r\n  'active': 'การดาวน์โหลด',\r\n  'waiting': 'กำลังรอ',\r\n  'stopped': 'หยุดแล้ว',\r\n  'new-task': 'งานใหม่',\r\n  'new-bt-task': 'งาน BT ใหม่',\r\n  'open-file': 'เปิดไฟล์ทอร์เรนต์...',\r\n  'uri-task': 'URL',\r\n  'torrent-task': 'ทอร์เรนต์',\r\n  'uri-task-tips': 'URL หนี่งงานต่อบรรทัด (รองรับแม่เหล็ก)',\r\n  'thunder-link-tips': 'เคล็ดลับ: ลิงก์ Thunder อาจไม่สามารถดาวน์โหลดได้หลังจากถอดรหัส',\r\n  'new-task-uris-required': 'โปรดป้อน URL ทรัพยากรที่ถูกต้องอย่างน้อยหนึ่งรายการ',\r\n  'new-task-torrent-required': 'โปรดเลือกไฟล์ทอร์เรนต์',\r\n  'file-name': 'ชื่อ',\r\n  'file-extension': 'นามสกุลไฟล์',\r\n  'file-size': 'ขนาด',\r\n  'file-completed-size': 'สำเร็จ',\r\n  'selected-files-sum': 'เลือกแล้ว: {{selectedFilesCount}} ไฟล์, ขนามรวม {{selectedFilesTotalSize}}',\r\n  'select-at-least-one': 'โปรดเลือกอย่างน้อยหนึ่งไฟล์',\r\n  'task-gid': 'GID',\r\n  'task-name': 'ชื่องาน',\r\n  'task-out': 'เปลี่ยนชื่อ',\r\n  'task-out-tips': 'ไม่จำเป็น',\r\n  'task-split': 'แบ่ง',\r\n  'task-dir': 'บันทึกไปที่',\r\n  'pause-task': 'พักงาน',\r\n  'task-ua': 'UA',\r\n  'task-user-agent': 'ตัวแทนผู้ใช้',\r\n  'task-authorization': 'การอนุญาต',\r\n  'task-referer': 'ผู้อ้างอิง',\r\n  'task-cookie': 'คุ้กกี้',\r\n  'task-proxy': 'พร็อกซี่',\r\n  'task-error-info': 'ผิดพลาด',\r\n  'task-piece': 'ชิ้นส่วน',\r\n  'task-piece-length': 'ขนาดชิ้น',\r\n  'task-num-pieces': 'ชิ้นส่วน',\r\n  'task-bittorrent-info': 'ข้อมูลทอร์เรนต์',\r\n  'task-info-hash': 'แฮช',\r\n  'task-bittorrent-creation-date': 'วันที่สร้าง',\r\n  'task-bittorrent-comment': 'ความคิดเห็น',\r\n  'task-progress-info': 'ความคืบหน้า',\r\n  'task-status': 'สถานะ',\r\n  'task-num-seeders': 'Seeders',\r\n  'task-connections': 'การเชื่อมต่อ',\r\n  'task-file-size': 'ขนาด',\r\n  'task-download-speed': 'ความเร็วดาวน์โหลด',\r\n  'task-upload-speed': 'ความเร็วอัพโหลด',\r\n  'task-download-length': 'ดาวน์โหลดแล้ว',\r\n  'task-upload-length': 'อัพโหลดแล้ว',\r\n  'task-ratio': 'อัตรา',\r\n  'task-peer-host': 'โฮสต์',\r\n  'task-peer-ip': 'IP',\r\n  'task-peer-client': 'ไคลเอนต์',\r\n  'navigate-to-downloading': 'นำทางไปยังการดาวน์โหลด',\r\n  'show-advanced-options': 'ตัวเลือกขั้นสูง',\r\n  'copyright-warning': 'คำเตือนเรื่องลิขสิทธิ์',\r\n  'copyright-warning-message': 'ไฟล์ที่คุณต้องการดาวน์โหลดอาจเป็นไฟล์เสียงหรือวิดีโอที่มีลิขสิทธิ์ โปรดตรวจสอบให้แน่ใจว่าคุณมีสิทธิ์ในการเข้าถึงไฟล์ดังกล่าว',\r\n  'copyright-yes': 'ใช่ ขออนุญาติแล้ว',\r\n  'copyright-no': 'ไม่ ฉันไม่ได้รับอนุญาต',\r\n  'copyright-error-message': 'ไม่สามารถเพิ่มงานเนื่องจากปัญหาลิขสิทธิ์',\r\n  'pause-task-success': 'หยุดงานชั่วคราวสำเร็จ \"{{taskName}}\"',\r\n  'pause-task-fail': 'ไม่สามารถหยุดงานชั่วคราว \"{{taskName}}\"',\r\n  'resume-task': 'ทำงานต่อ',\r\n  'resume-task-success': 'กลับมาทำงานต่อได้สำเร็จ \"{{taskName}}\"',\r\n  'resume-task-fail': 'ไม่สามารถทำงานต่อได้ \"{{taskName}}\"',\r\n  'delete-task': 'ลบงาน',\r\n  'delete-selected-tasks': 'ลบงานที่เลือก',\r\n  'delete-task-confirm': 'คุณแน่ใจหรือว่าต้องการลบงานดาวน์โหลด \"{{taskName}}\"?',\r\n  'batch-delete-task-confirm': 'คุณแน่ใจหรือว่าต้องการลบดาวน์โหลด {{count}} งานในชุด?',\r\n  'delete-task-label': 'ลบพร้อมไฟล์',\r\n  'delete-task-success': 'ลบงานเรียบร้อยแล้ว \"{{taskName}}\"',\r\n  'delete-task-fail': 'ไม่สามารถลบงาน \"{{taskName}}\"',\r\n  'remove-task-file-fail': 'ลบไฟล์งานไม่สำเร็จ โปรดลบด้วยตนเอง',\r\n  'remove-task-config-file-fail': 'ลบไฟล์กำหนดค่างานไม่สำเร็จ โปรดลบด้วยตนเอง',\r\n  'move-task-up': 'ย้ายงานขึ้น',\r\n  'move-task-down': 'ย้ายงานลง',\r\n  'pause-all-task': 'หยุดงานทั้งหมดชั่วคราว',\r\n  'pause-all-task-success': 'หยุดงานทั้งหมดชั่วคราวเรียบร้อยแล้ว',\r\n  'pause-all-task-fail': 'ไม่สามารถหยุดงานทั้งหมดชั่วคราว',\r\n  'resume-all-task': 'ทำงานทั้งหมดต่อ',\r\n  'resume-all-task-success': 'กลับมาทำงานทั้งหมดต่อได้สำเร็จ',\r\n  'resume-all-task-fail': 'ไม่สามารถทำงานทั้งหมดต่อได้',\r\n  'select-all-task': 'เลือกงานทั้งหมด',\r\n  'clear-recent-tasks': 'ล้างงานล่าสุด',\r\n  'purge-record': 'ล้างบันทึกงาน',\r\n  'purge-record-success': 'ลบบันทึกงานเรียบร้อยแล้ว',\r\n  'purge-record-fail': 'ไม่สามารถล้างบันทึกงาน',\r\n  'batch-delete-task-success': 'ลบงานในแบตช์สำเร็จ',\r\n  'batch-delete-task-fail': 'ไม่สามารถลบงานในชุดงาน',\r\n  'refresh-list': 'รีเฟรชรายการงาน',\r\n  'no-task': 'ไม่มีงานปัจจุบัน',\r\n  'copy-link': 'คัดลอกลิงค์',\r\n  'copy-link-success': 'คัดลอกลิงค์สำเร็จ',\r\n  'remove-record': 'ลบบันทึกงาน',\r\n  'remove-record-confirm': 'คุณแน่ใจหรือว่าต้องการลบบันทึกการดาวน์โหลดสำหรับ \"{{taskName}}\"?',\r\n  'remove-record-label': 'ลบด้วยไฟล์',\r\n  'remove-record-success': 'ลบบันทึกงานสำหรับ \"{{taskName}}\" เรียบร้อยแล้ว',\r\n  'remove-record-fail': 'ไม่สามารถลบบันทึกงานสำหรับ \"{{taskName}}\"',\r\n  'show-in-folder': 'แสดงงานในโฟลเดอร์',\r\n  'file-not-exist': 'ไฟล์เป้าหมายไม่มีอยู่หรือถูกลบไปแล้ว',\r\n  'file-path-error': 'ข้อผิดพลาดเส้นทางไฟล์',\r\n  'opening-task-message': 'กำลังเปิด \"{{taskName}}\" ...',\r\n  'get-task-name': 'รับชื่องาน...',\r\n  'remaining-prefix': 'ที่เหลืออยู่',\r\n  'select-torrent': 'ลากไฟล์ทอร์เรนต์มาที่นี่ หรือคลิกเพื่อเลือก',\r\n  'task-detail-title': 'รายละเอียดงาน',\r\n  'task-info-dialog-title': 'รายละเอียด {{title}}',\r\n  'download-start-message': 'เริ่มดาวน์โหลดแล้ว {{taskName}}',\r\n  'download-pause-message': 'หยุดการดาวน์โหลดชั่วคราว {{taskName}}',\r\n  'download-stop-message': 'หยุดดาวน์โหลด {{taskName}}',\r\n  'download-error-message': 'เกิดข้อผิดพลาดขณะดาวน์โหลด {{taskName}}',\r\n  'download-complete-message': 'ดาวน์โหลดเสร็จแล้ว {{taskName}}',\r\n  'download-complete-notify': 'ดาวน์โหลดเสร็จแล้ว',\r\n  'bt-download-complete-message': 'ดาวน์โหลดเสร็จแล้ว {{taskName}}, กำลัง seed',\r\n  'bt-download-complete-notify': 'BT ดาวน์โหลดเสร็จแล้ว, กำลัง seed...',\r\n  'bt-download-complete-tips': 'เคล็ดลับ: คุณสามารถหยุดงานเพื่อสิ้นสุดการ seed ได้',\r\n  'bt-stopping-seeding-tip': 'กำลังหยุดการ seed จะใช้เวลาสักพักในการตัดการเชื่อมต่อ โปรดรอสักครู่...',\r\n  'download-fail-message': 'ไม่สามารถดาวน์โหลด {{taskName}}',\r\n  'download-fail-notify': 'ดาวน์โหลดล้มเหลว'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/th/window.js",
    "content": "export default {\r\n  'reload': 'โหลดใหม่',\r\n  'close': 'ปิด',\r\n  'minimize': 'ย่อหน้าต่างเล็ก',\r\n  'zoom': 'ขยาย',\r\n  'toggle-fullscreen': 'เข้าสู่เต็มหน้าจอ',\r\n  'front': 'นำทั้งหมดมาไว้ข้างหน้า'\r\n}\r\n"
  },
  {
    "path": "src/shared/locales/tr/about.js",
    "content": "export default {\n  'engine-version': 'Engine Versiyonu',\n  'license': 'Lisans',\n  'about': 'Hakkında',\n  'release': 'Sürüm',\n  'support': 'Destek'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/app.js",
    "content": "export default {\n  'task-list': 'Görevler',\n  'add-task': 'Görev Ekle',\n  'about': 'Motrix Hakkında',\n  'preferences': 'Ayarlar...',\n  'check-for-updates': 'Güncellemeleri kontrol et...',\n  'check-updates-now': 'Şimdi kontrol et',\n  'checking-for-updates': 'Güncellemeleri kontrol ediyor ...',\n  'check-for-updates-title': 'Güncellemeleri kontrol et',\n  'update-available-message': 'Motrix\\'in yeni bir sürümü var, şimdi güncelle?',\n  'update-not-available-message': 'Gündemdesin!',\n  'update-downloaded-message': 'Yüklemeye hazır...',\n  'update-error-message': 'Güncelleme Hatası',\n  'engine-damaged-message': 'Motor hasarlı, lütfen tekrar takın : (',\n  'engine-missing-message': 'Motor eksik, lütfen tekrar takın : (',\n  'system-error-title': 'Sistem hatası',\n  'system-error-message': 'Uygulama başlatılamadı: {{message}}',\n  'hide': 'Motrix\\'i gizle',\n  'hide-others': 'Diğerlerini gizle',\n  'unhide': 'Hepsini Göster',\n  'show': 'Motrix\\'i Göster',\n  'quit': 'Motrix\\'ten çık',\n  'under-development-message': 'Üzgünüz, bu özellik geliştirme aşamasında...',\n  'yes': 'Evet',\n  'no': 'Hayır',\n  'save': 'Kayıt etmek',\n  'reset': 'İptal Et',\n  'cancel': 'İptal',\n  'submit': 'Gönder',\n  'gt1d': '> 1 gün',\n  'hour': 'S',\n  'minute': 'd',\n  'second': 's'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/edit.js",
    "content": "export default {\n  'undo': 'Geri al',\n  'redo': 'Yinele',\n  'cut': 'Kes',\n  'copy': 'Kopyala',\n  'paste': 'Yapıştır',\n  'delete': 'Sil',\n  'select-all': 'Hepsini seç'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/help.js",
    "content": "export default {\n  'official-website': 'Motrix Web Sitesi',\n  'manual': 'Kılavuz',\n  'release-notes': 'Sürüm Notları...',\n  'report-problem': 'Sorun bildir',\n  'toggle-dev-tools': 'Geliştirici Aracına geçiş yap'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/tr/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Dosya',\n  'task': 'Görev',\n  'edit': 'Düzenle',\n  'window': 'Pencere',\n  'help': 'Yardım'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/preferences.js",
    "content": "export default {\n  'basic': 'Temel',\n  'advanced': 'Gelişmiş',\n  'lab': 'Deneysel',\n  'save': 'Kaydet & Uygula',\n  'save-success-message': 'Tercihleri başarıyla kaydedin',\n  'save-fail-message': 'Tercihleri kaydetme başarısız oldu',\n  'discard': 'İptal Et',\n  'startup': 'Başlangıçta',\n  'open-at-login': 'Giriş sırasında aç',\n  'keep-window-state': 'Pencerenin boyutunu ve konumunu geri yükleyin',\n  'auto-resume-all': 'Tüm bitmemiş görevleri otomatik olarak devam ettir',\n  'default-dir': 'Varsayılan Klasör',\n  'mas-default-dir-tips': 'App Store\\'un sanal alan izinleri kısıtlamaları nedeniyle, varsayılan indirme dizininin İndirilenler dizinine ayarlanması önerilir.',\n  'transfer-settings': 'İletim',\n  'transfer-speed-upload': 'Yükleme limiti',\n  'transfer-speed-download': 'İndirme limiti',\n  'transfer-speed-unlimited': 'Sınırsız',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Mıknatıs bağlantısını torrent dosyası olarak kaydedin',\n  'bt-auto-download-content': 'Magnet ve torrent içeriğini otomatik olarak indirin',\n  'bt-force-encryption': 'BT Zorunlu Şifreleme',\n  'keep-seeding': 'Manuel olarak durdurana kadar tohumlamaya devam edin',\n  'seed-ratio': 'Tohum Oranı',\n  'seed-time': 'Tohum Zamanı',\n  'seed-time-unit': 'dakika',\n  'task-manage': 'Görev Yöneticisi',\n  'max-concurrent-downloads': 'Maksimum aktif görev',\n  'max-connection-per-server': 'Sunucu başına maksimum bağlantı',\n  'new-task-show-downloading': 'Görev ekledikten sonra indirmeyi otomatik göster',\n  'no-confirm-before-delete-task': 'Görevi silmeden önce onay gerekmez',\n  'continue': 'Devamlı',\n  'task-completed-notify': 'İndirme bittikten sonra bildirim göster',\n  'auto-purge-record': 'Auto purge download record when app exit',\n  'ui': 'Kullanıcı Arayüzü',\n  'appearance': 'Görünüş',\n  'theme-auto': 'Otomatik',\n  'theme-light': 'Açık',\n  'auto-hide-window': 'Pencereleri otomatik gizle',\n  'run-mode': 'Olarak çalıştırmak',\n  'run-mode-standard': 'Standart Uygulama',\n  'run-mode-tray': 'Tepsi Uygulaması',\n  'theme-dark': 'Koyu',\n  'run-mode-hide-tray': 'Tepsi uygulamasını gizle',\n  'tray-speedometer': 'Menü çubuğu tepsisi gerçek zamanlı hızı gösterir',\n  'show-progress-bar': 'İndirme ilerleme çubuğunu göster',\n  'language': 'Dil',\n  'change-language': 'Dil değiştir',\n  'hide-app-menu': 'Uygulama menüsünü göster (Windows & Linux için)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Proxy etkinleştir',\n  'proxy-bypass-input-tips': 'Her bir satırda bir tane olacak şekilde bu Ana Bilgisayarlar ve Alanlar için proxy ayarlarını atlayın',\n  'proxy-scope-download': 'İndirme',\n  'proxy-scope-update-app': 'Uygulamayı Güncelle',\n  'proxy-scope-update-trackers': 'İzleyicileri Güncelle',\n  'proxy-tips': 'Proxy Kılavuzunu Görüntüle',\n  'bt-tracker': 'İzleyici Sunucular',\n  'bt-tracker-input-tips': 'İzleyici sunucusu, her satıra bir tane',\n  'bt-tracker-tips': 'Tavsiye et:',\n  'sync-tracker-tips': 'Senkronizasyon',\n  'auto-sync-tracker': 'İzleyici listesini her gün otomatik olarak güncelleyin',\n  'port': 'Bağlantı noktalarını dinleyin',\n  'bt-port': 'BT dinleme bağlantı noktası',\n  'dht-port': 'DHT dinleme bağlantı noktası',\n  'security': 'Güvenlik',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC Dinleme Portu',\n  'rpc-secret': 'RPC sırrı',\n  'rpc-secret-tips': 'RPC gizli kılavuzunu görüntüle',\n  'developer': 'Geliştirici',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Sahte Kullanıcı Kimliği (User-Agent)',\n  'aria2-conf-path': 'Dahili aria2.conf yolu',\n  'app-log-path': 'Uygulama log yolu',\n  'download-session-path': 'Oturum yolunu indir',\n  'factory-reset': 'Fabrika ayarlarına dön',\n  'factory-reset-confirm': 'Fabrika ayarlarına geri dönmek istediğinize emin misiniz?',\n  'lab-warning': '⚠️ Deneysel özellikleri etkinleştirmek uygulamanın çökmesine veya veri kaybına neden olabilir, kendiniz karar verin!',\n  'download-protocol': 'Protokol',\n  'protocols-default-client': 'Aşağıdaki protokoller için varsayılan istemci olarak ayarla',\n  'protocols-magnet': 'Mıknatıs [ magnet:// ]',\n  'protocols-thunder': 'gök gürültüsü [ thunder:// ]',\n  'browser-extensions': 'Eklentiler',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Topluluk tarafından sağlanan, ',\n  'baidu-exporter-help': 'Kullanım detayları için buraya tıklayın',\n  'auto-update': 'Otomatik güncelleme',\n  'auto-check-update': 'Otomatik Kontrol Güncellemesi',\n  'last-check-update-time': 'Son Kontrol Güncelleme Saati',\n  'not-saved': 'Tercihler kaydedilmedi',\n  'not-saved-confirm': 'Değiştirilen tercihler kaybolacak, ayrılacağınızdan emin misiniz?'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/subnav.js",
    "content": "export default {\n  'task-list': 'Görevler',\n  'preferences': 'Ayarlar'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/task.js",
    "content": "export default {\n  'active': 'İndiriliyor',\n  'waiting': 'Bekleniyor',\n  'stopped': 'Durdu',\n  'new-task': 'Yeni Görev',\n  'new-bt-task': 'Yeni BT Görevi',\n  'open-file': 'Torrent Dosyasını Aç...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Her bir satır için bir görev (magnet destekli)',\n  'thunder-link-tips': 'İpucu: Thunder bağlantıları kod çözme işleminden sonra indirilemeyebilir',\n  'new-task-uris-required': 'Lütfen en az bir geçerli kaynak URL adresi girin',\n  'new-task-torrent-required': 'Lütfen bir torrent dosyası seçin',\n  'file-name': 'Dosya Adı',\n  'file-extension': 'uzantı',\n  'file-size': 'Boyut',\n  'file-completed-size': 'İndirildi',\n  'selected-files-sum': 'Seçildi: {{selectedFilesCount}} dosya sayısı, total {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Lütfen en az bir dosya seçin',\n  'task-gid': 'GID',\n  'task-name': 'Görev Adı',\n  'task-out': 'Dosya Adı',\n  'task-out-tips': 'Opsiyonel',\n  'task-split': 'Parça',\n  'task-dir': 'Yol',\n  'pause-task': 'Görevi Durdur',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Yetkilendirme',\n  'task-referer': 'Yönlendiren',\n  'task-cookie': 'Çerez',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Hata',\n  'task-piece': 'Parça',\n  'task-piece-length': 'Parça Boyutu',\n  'task-num-pieces': 'Adet',\n  'task-bittorrent-info': 'Torrent Bilgisi',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Oluşturulma tarihi',\n  'task-bittorrent-comment': 'Yorum Yap',\n  'task-progress-info': 'İlerleme',\n  'task-status': 'Durum',\n  'task-num-seeders': 'Ekme makineleri',\n  'task-connections': 'Bağlantılar',\n  'task-file-size': 'Boyut',\n  'task-download-speed': 'İndirme hızı',\n  'task-upload-speed': 'Yükleme hızı',\n  'task-download-length': 'İndirildi',\n  'task-upload-length': 'Yüklendi',\n  'task-ratio': 'Oran',\n  'task-peer-host': 'Ev sahibi',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Müşteri',\n  'navigate-to-downloading': 'İndirilenlere git',\n  'show-advanced-options': 'Gelişmiş ayarlar',\n  'copyright-warning': 'Telif Uyarısı',\n  'copyright-warning-message': 'İndirmek istediğiniz dosya telif hakkıyla korunan ses veya videoya ait olabilir, lütfen telif hakkı lisansına sahip olduğunuzdan emin olun.',\n  'copyright-yes': 'Evet, sahibim',\n  'copyright-no': 'Hayır',\n  'copyright-error-message': 'Telif hakkı sorunları nedeniyle görev ekleme işlemi başarısız oldu',\n  'pause-task-success': '\"{{taskName}}\" görevi durduruldu',\n  'pause-task-fail': '\"{{taskName}}\" görevi durdurulamadı',\n  'resume-task': 'Görevi sürdür',\n  'resume-task-success': '\"{{taskName}}\" devam ettirildi',\n  'resume-task-fail': '\"{{taskName}}\" devam ettirilemedi',\n  'delete-task': 'Görevi sil',\n  'delete-selected-tasks': 'Seçilen görevleri sil',\n  'delete-task-confirm': '\"{{taskName}}\" adlı görevi silmek istediğinizden emin misiniz?',\n  'batch-delete-task-confirm': '{{count}} indirme görevini toplu olarak kaldırmak istediğinizden emin misiniz?',\n  'delete-task-label': 'Dosyalar ile birlikte sil',\n  'delete-task-success': '\"{{taskName}}\" silindi',\n  'delete-task-fail': '\"{{taskName}}\" silinemedi',\n  'remove-task-file-fail': 'Görev dosyaları silinemedi, lütfen manuel olarak silin',\n  'remove-task-config-file-fail': 'Görev yapılandırma dosyası silinemedi, lütfen manuel olarak silin',\n  'move-task-up': 'Görevi yukarı taşı',\n  'move-task-down': 'Görevi aşağı taşı',\n  'pause-all-task': 'Bütün görevleri durdur',\n  'pause-all-task-success': 'Bütün görevleri durdurma başarılı',\n  'pause-all-task-fail': 'Bütün görevleri durdurma başarısız',\n  'resume-all-task': 'Bütün görevleri sürdür',\n  'resume-all-task-success': 'Bütün görevleri sürdürme başarılı',\n  'resume-all-task-fail': 'Bütün görevleri sürdürme başarısız',\n  'select-all-task': 'Tüm görevi seçin',\n  'clear-recent-tasks': 'Son görevleri temizle',\n  'purge-record': 'Görev Kaydını Temizle',\n  'purge-record-success': 'Görev Kaydını Temizleme başarılı',\n  'purge-record-fail': 'Görev Kaydını Temizleme başarısız',\n  'batch-delete-task-success': 'Toplu işteki görevleri başarıyla silin',\n  'batch-delete-task-fail': 'Toplu işteki görevler silinemedi',\n  'refresh-list': 'Görev listesini yenile',\n  'no-task': 'Görev yok',\n  'copy-link': 'Bağlantıyı kopyala',\n  'copy-link-success': 'Bağlantı Kopyalama başarılı',\n  'remove-record': 'Görev kaydını kaldır',\n  'remove-record-confirm': '\"{{taskName}}\" görevinin kaydın kaldırmak istediğinizden emin misiniz?',\n  'remove-record-label': 'Dosyalar ile birlikte kaldır',\n  'remove-record-success': '\"{{taskName}}\" kayıtları kaldırıldı',\n  'remove-record-fail': '\"{{taskName}}\" kayıtları kaldırılamadı',\n  'show-in-folder': 'Görevi klasörde göster',\n  'file-not-exist': 'Dosya yok ya da silinmiş',\n  'file-path-error': 'Dosya yolu hatası',\n  'opening-task-message': '\"{{taskName}}\" açılıyor ...',\n  'get-task-name': 'Görev ismi getir...',\n  'remaining-prefix': 'Kalan Süre',\n  'select-torrent': 'Torrent dosyasını sürükleyin ya da seçmek için tıklayın',\n  'task-info-dialog-title': '{{title}} hakkında bilgiler',\n  'download-start-message': '{{taskName}} görevi başlatıldı',\n  'download-pause-message': '{{taskName}} görevi duraklatıldı',\n  'download-stop-message': '{{taskName}} görevi durduruldu',\n  'download-error-message': '{{taskName}} görevinde hata oluştu',\n  'download-complete-message': '{{taskName}} görevi tamamlandı',\n  'download-complete-notify': 'İndirme bitti',\n  'bt-download-complete-message': '{{taskName}} indirme tamamlandı, tohumlama...',\n  'bt-download-complete-notify': 'BT Indirme tamamlandı, tohumlama...',\n  'bt-download-complete-tips': 'Ipuçları: Eğer tohumlama sona erdirmek için görev durdurabilirsiniz',\n  'bt-stopping-seeding-tip': 'Ekim işlemini durdurmak, bağlantıyı kesmek biraz zaman alacak, lütfen bekleyin...',\n  'download-fail-message': '{{taskName}} görevi indirilemedi',\n  'download-fail-notify': 'İndirme başarısız'\n}\n"
  },
  {
    "path": "src/shared/locales/tr/window.js",
    "content": "export default {\n  'reload': 'Yenile',\n  'close': 'Kapat',\n  'minimize': 'Küçült',\n  'zoom': 'Yakınlaştır',\n  'toggle-fullscreen': 'Tam ekran yap',\n  'front': 'Tümünü Öne Getir'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/about.js",
    "content": "export default {\n  'engine-version': 'Версія двигуна',\n  'license': 'Ліцензія',\n  'about': 'Інформація',\n  'release': 'Реліз',\n  'support': 'Підтримка'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/app.js",
    "content": "export default {\n  'task-list': 'Завдання',\n  'add-task': 'Додати завдання',\n  'about': 'Про Motrix',\n  'preferences': 'Налаштування...',\n  'check-for-updates': 'Перевірити оновлення...',\n  'check-updates-now': 'Перевірити зараз',\n  'checking-for-updates': 'Перевірка оновлень ...',\n  'check-for-updates-title': 'Перевірити оновлення',\n  'update-available-message': 'Нова версія Motrix доступна для завантаження, завантажити зараз?',\n  'update-not-available-message': 'У вас вже є остання версія!',\n  'update-downloaded-message': 'Готово для встановлення...',\n  'update-error-message': 'Помилка оновлення',\n  'engine-damaged-message': 'Двигун пошкоджено, будь ласка переінсталюйте : (',\n  'engine-missing-message': 'Двигун загублено, будь ласка переінсталюйте : (',\n  'system-error-title': 'Системна помилка',\n  'system-error-message': 'Помилка запуску додатка: {{message}}',\n  'hide': 'Сховати Motrix',\n  'hide-others': 'Сховати інше',\n  'unhide': 'Відобразити все',\n  'show': 'Відобразити Motrix',\n  'quit': 'Закрити Motrix',\n  'under-development-message': 'На жаль, ця функція розробляється...',\n  'yes': 'Так',\n  'no': 'Ні',\n  'save': 'Зберегти',\n  'reset': 'Відмінити',\n  'cancel': 'Відмінити',\n  'submit': 'Підтвердити',\n  'gt1d': '> 1 день',\n  'hour': 'г',\n  'minute': 'х',\n  'second': 'с'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/edit.js",
    "content": "export default {\n  'undo': 'Відміна',\n  'redo': 'Повторити',\n  'cut': 'Вирізати',\n  'copy': 'Копіювати',\n  'paste': 'Вставити',\n  'delete': 'Видалити',\n  'select-all': 'Обрати все'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/help.js",
    "content": "export default {\n  'official-website': 'Сайт Motrix',\n  'manual': 'Інструкція',\n  'release-notes': 'Примітки до релизу...',\n  'report-problem': 'Повідомити про проблему',\n  'toggle-dev-tools': 'Переключити інстрменти розробника'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/uk/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Файл',\n  'task': 'Завдання',\n  'edit': 'Редагування',\n  'window': 'Вікно',\n  'help': 'Допомога'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/preferences.js",
    "content": "export default {\n  'basic': 'Основні',\n  'advanced': 'Розширені',\n  'lab': 'Лабораторія',\n  'save': 'Зберігти та Застосувати',\n  'save-success-message': 'Налаштування збережено успішно',\n  'save-fail-message': 'Помилка при зберіганні налаштувань',\n  'discard': 'Відмінити',\n  'startup': 'Стартап',\n  'open-at-login': 'Запускати програму разом з запуском операційної системи',\n  'keep-window-state': 'Під час закриття додатка, зберігати розмір і положення вікна',\n  'auto-resume-all': 'Автоматично поновлювати всі невиконанні завдання',\n  'default-dir': 'Шлях за замовчуванням',\n  'mas-default-dir-tips': 'Через обмеження в App Store, рекомендовано встановити шлях за замовчення ~/Downloads',\n  'transfer-settings': 'Передача',\n  'transfer-speed-upload': 'Ліміт вивантаження',\n  'transfer-speed-download': 'Ліміт завантаження',\n  'transfer-speed-unlimited': 'Безлімітно',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Зберегти магнітне посилання як торрент-файл',\n  'bt-auto-download-content': 'Автоматично завантажуйте вміст магніту та торрент',\n  'bt-force-encryption': 'Обов\\'язкова криптографія BT',\n  'keep-seeding': 'Продовжуйте висівати, поки не зупините його вручну',\n  'seed-ratio': 'Співвідношення насіння',\n  'seed-time': 'Час насіння',\n  'seed-time-unit': 'хвилин',\n  'task-manage': 'Менеджер завдань',\n  'max-concurrent-downloads': 'Максимум активних завдань',\n  'max-connection-per-server': 'Максимум з\\'єднання на сервер',\n  'new-task-show-downloading': 'Автоматично відображати завантаження після додавання завдання',\n  'no-confirm-before-delete-task': 'Перед видаленням завдання не потрібно підтверджувати',\n  'continue': 'Продовжити',\n  'task-completed-notify': 'Повідомлення після завершення завантаження',\n  'auto-purge-record': 'Автоматично чистити записи про завантаження перед закриттям додатка',\n  'ui': 'UI',\n  'appearance': 'Зовнішній вигляд',\n  'theme-auto': 'Автоматично',\n  'theme-light': 'Світлий',\n  'theme-dark': 'Темний',\n  'auto-hide-window': 'Автозахист вікон',\n  'run-mode': 'Виконати як',\n  'run-mode-standard': 'Стандартне застосування',\n  'run-mode-tray': 'Додаток у лотку',\n  'run-mode-hide-tray': 'Приховати програму лотка',\n  'tray-speedometer': 'Лоток панелі меню показує швидкість у режимі реального часу',\n  'show-progress-bar': 'Показати панель завантаження',\n  'language': 'Мова',\n  'change-language': 'Змінити мову',\n  'hide-app-menu': 'Сховати меню додатка (Тільки для Windows та Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Застосувати Proxy',\n  'proxy-bypass-input-tips': 'Обхід налаштувань проксі для цих хостів та доменів, по одному на рядок',\n  'proxy-scope-download': 'Завантажити',\n  'proxy-scope-update-app': 'Оновити додаток',\n  'proxy-scope-update-trackers': 'Оновити трекери',\n  'proxy-tips': 'Перегляньте посібник з проксі',\n  'bt-tracker': 'Tracker Сервер',\n  'bt-tracker-input-tips': 'Tracker сервера, один в рядок',\n  'bt-tracker-tips': 'Рекомендовано: ',\n  'sync-tracker-tips': 'Сінхронізуватись',\n  'auto-sync-tracker': 'Щодня оновлюйте список трекерів автоматично',\n  'port': 'Слухайте порти',\n  'bt-port': 'Порт прослуховування BT',\n  'dht-port': 'Порт прослуховування DHT',\n  'security': 'Безпека',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Порт прослуховування RPC',\n  'rpc-secret': 'RPC Secret',\n  'rpc-secret-tips': 'Дивитись інструкцію RPC Secret',\n  'developer': 'Розробник',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Макет User-Agent',\n  'aria2-conf-path': 'Вбудований шлях до aria2.conf',\n  'app-log-path': 'Шлях до журналу додатка',\n  'download-session-path': 'Завантажити шлях сесії',\n  'factory-reset': 'Налаштування за замовчуванням',\n  'factory-reset-confirm': 'Ви впевненні, що бажаєте повернутись до налаштувань за замовчуванням?',\n  'lab-warning': '⚠️ Увімкнення функцій лабораторії може призвести до збою програми або втрати даних, вирішити на власний ризик!',\n  'download-protocol': 'Протоколи',\n  'protocols-default-client': 'Встановіть як клієнта за замовчуванням для таких протоколів',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Розширення',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Надається спільнотою, ',\n  'baidu-exporter-help': 'Натисніть тут для використання',\n  'auto-update': 'Автоматичне оновлення',\n  'auto-check-update': 'Автоматично перевіряти оновлення',\n  'last-check-update-time': 'В останнє оновлення перевірялось',\n  'not-saved': 'Налаштування не збережено',\n  'not-saved-confirm': 'Змінені параметри буде втрачено. Ви впевнені, що залишите?'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/subnav.js",
    "content": "export default {\n  'task-list': 'Завдання',\n  'preferences': 'Налаштування'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/task.js",
    "content": "export default {\n  'active': 'Завантаження',\n  'waiting': 'Очікування',\n  'stopped': 'Зупинено',\n  'new-task': 'Нове завдання',\n  'new-bt-task': 'Нове BT завдання',\n  'open-file': 'Відкрити Torrent файл...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Одне URL-задання на рядок (підтримуе magnet)',\n  'thunder-link-tips': 'Порада: Посилання типу Thunder може не завантажуватися після декодування',\n  'new-task-uris-required': 'Введіть принаймні один дійсний URL-адресу ресурсу',\n  'new-task-torrent-required': 'Будь ласка оберіть torrent файл',\n  'file-name': 'Ім\\'я файлу',\n  'file-extension': 'Тип файлу',\n  'file-size': 'Розмір',\n  'file-completed-size': 'Завершений',\n  'selected-files-sum': 'Обрано: {{selectedFilesCount}} файлів, загальний розмір {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Виберіть принаймні один файл',\n  'task-gid': 'GID',\n  'task-name': 'Ім\\'я завдання',\n  'task-out': 'Перейменувати',\n  'task-out-tips': 'Необов\\'язковий',\n  'task-split': 'Розбити',\n  'task-dir': 'Зберігти в',\n  'pause-task': 'Призупинити завдання',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Авторизація',\n  'task-referer': 'Реферал',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Помилка',\n  'task-piece': 'Шматок',\n  'task-piece-length': 'Розмір шматка',\n  'task-num-pieces': 'Шматочки',\n  'task-bittorrent-info': 'Інформація про торрент',\n  'task-info-hash': 'Хеш',\n  'task-bittorrent-creation-date': 'Дата створення',\n  'task-bittorrent-comment': 'Прокоментуйте',\n  'task-progress-info': 'Прогрес',\n  'task-status': 'Статус',\n  'task-num-seeders': 'Сівалки',\n  'task-connections': 'Зв\\'язки',\n  'task-file-size': 'Розмір',\n  'task-download-speed': 'Швидкість завантаження',\n  'task-upload-speed': 'Швидкість завантаження',\n  'task-download-length': 'Завантажено',\n  'task-upload-length': 'Завантажено',\n  'task-ratio': 'Співвідношення',\n  'task-peer-host': 'Ведучий',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Клієнт',\n  'navigate-to-downloading': 'Перейти до завантаження',\n  'show-advanced-options': 'Розширенні опції',\n  'copyright-warning': 'Попередження про авторські права',\n  'copyright-warning-message': 'Файл який ви намагаєтесь завантажити можливо має авторські права на аудіо або відео, будь ласка перевірте чи ви маєте повноваження на доступ до цього файлу.',\n  'copyright-yes': 'Так, я маю повноваження',\n  'copyright-no': 'Ні, в мене нема повноваженнь',\n  'copyright-error-message': 'Помилка при додаванні завдання, через проблему за авторськими правами',\n  'pause-task-success': 'Успішно призупинине завдання \"{{taskName}}\"',\n  'pause-task-fail': 'Помилка призупинення завдання \"{{taskName}}\"',\n  'resume-task': 'Поновити Завдання',\n  'resume-task-success': 'Успішно поновленне завдання \"{{taskName}}\"',\n  'resume-task-fail': 'Помилка при поновленні завдання \"{{taskName}}\"',\n  'delete-task': 'Видалити завдання',\n  'delete-selected-tasks': 'Видалити обранне завдання',\n  'delete-task-confirm': 'Ви впевненні що бажаєте видалити завдання \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Ви впевнені, що хочете видалити {{count}} завдань із завантаження в пакеті?',\n  'delete-task-label': 'Видалити разом з файлами',\n  'delete-task-success': 'Успішно видалине завдання \"{{taskName}}\"',\n  'delete-task-fail': 'Помилка при видаленні завдання \"{{taskName}}\"',\n  'remove-task-file-fail': 'Поимилка при видаленні файла(файлів) завдання, будь ласка видаліть його(їх) самостійно',\n  'remove-task-config-file-fail': 'Помилка пи видаленні файла конфігурації завдання, будьласка видаліть його самостійно',\n  'move-task-up': 'Пермістити завдання в гору',\n  'move-task-down': 'Перемістити завдання в низ',\n  'pause-all-task': 'Призупинити всі завдання',\n  'pause-all-task-success': 'Успішно призупиненні всі завдання',\n  'pause-all-task-fail': 'Помилка при призупиненні всіх завдань',\n  'resume-all-task': 'Відновити всі завдання',\n  'resume-all-task-success': 'Успішно поновленні всі завдання',\n  'resume-all-task-fail': 'Помилка при поновленні всіх завдань',\n  'select-all-task': 'Виберіть усе завдання',\n  'clear-recent-tasks': 'Очистити останні завдання',\n  'purge-record': 'Очистити записи про завдання',\n  'purge-record-success': 'Успішно очишенні записи про завдання',\n  'purge-record-fail': 'Помилка при очишенні записів про завдання',\n  'batch-delete-task-success': 'Видалити завдання в пакеті',\n  'batch-delete-task-fail': 'Не вдалося видалити завдання з партії',\n  'refresh-list': 'Оновити список завдань',\n  'no-task': 'Нема поточних завдань',\n  'copy-link': 'Копіювати посилання',\n  'copy-link-success': 'Успішно скопійоване посилання',\n  'remove-record': 'Видалити запис про завдання',\n  'remove-record-confirm': 'Ви впевненні, що бажаєте видалити запис про завантаження \"{{taskName}}\"?',\n  'remove-record-label': 'Видалити разом з файлами',\n  'remove-record-success': 'Успішно видалено запис про завдання \"{{taskName}}\"',\n  'remove-record-fail': 'Помилка при видаленні запису про завдання \"{{taskName}}\"',\n  'show-in-folder': 'Відобразити файли завдання у папці',\n  'file-not-exist': 'Розшукуємий файл не існує або був видаленний',\n  'file-path-error': 'Помилка шляху файла',\n  'opening-task-message': 'Відкриття \"{{taskName}}\" ...',\n  'get-task-name': 'Отримання Ім\\'я завдання...',\n  'remaining-prefix': 'Залишилося',\n  'select-torrent': 'Перетягніть torrent файл сюди, або натисніть обрати',\n  'task-info-dialog-title': '{{title}} Деталі',\n  'download-start-message': 'Почалось завантаження {{taskName}}',\n  'download-pause-message': 'Призупинине завантаження {{taskName}}',\n  'download-stop-message': 'Зупинине завантаження {{taskName}}',\n  'download-error-message': 'Помилка під час завантаження {{taskName}}',\n  'download-complete-message': 'Виконане завантаження {{taskName}}',\n  'download-complete-notify': 'Завантаження виконане',\n  'bt-download-complete-message': 'Виконане завантаження {{taskName}}, раздача',\n  'bt-download-complete-notify': 'BT Виконане завантаження, роздача...',\n  'bt-download-complete-tips': 'Порада: Ви можите зупинити завдання щоб зупинити роздачу',\n  'bt-stopping-seeding-tip': 'Припиняючи посів, потрібно буде трохи часу відключити, зачекайте, будь ласка ...',\n  'download-fail-message': 'Не вдалося завантажити {{taskName}}',\n  'download-fail-notify': 'Помилка завантаження'\n}\n"
  },
  {
    "path": "src/shared/locales/uk/window.js",
    "content": "export default {\n  'reload': 'Перезавнтажити',\n  'close': 'Закрити',\n  'minimize': 'Згорнути',\n  'zoom': 'Збільшення',\n  'toggle-fullscreen': 'Перейти до повноекранного режиму',\n  'front': 'Поверх всіх вікон'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/about.js",
    "content": "export default {\n  'engine-version': 'Phiên bản Ứng dụng',\n  'license': 'Giấy phép',\n  'about': 'Về Motrix',\n  'release': 'Phát hành',\n  'support': 'Hỗ trợ'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/app.js",
    "content": "export default {\n  'task-list': 'Danh sách Tác vụ',\n  'add-task': 'Thêm tác vụ',\n  'about': 'Về Motrix',\n  'preferences': 'Cài đặt...',\n  'check-for-updates': 'Kiểm tra cập nhật...',\n  'check-updates-now': 'Kiểm tra ngay',\n  'checking-for-updates': 'Đang kiểm tra cập nhật...',\n  'check-for-updates-title': 'Kiểm tra Cập nhật',\n  'update-available-message': 'Có một phiên bản mới của Motrix, cập nhật ngay?',\n  'update-not-available-message': 'Bạn đang dùng bản mới nhất!',\n  'update-downloaded-message': 'Đã sẵn sàng để cài đặt...',\n  'update-error-message': 'Cập nhật lỗi',\n  'engine-damaged-message': 'Ứng dụng bị lỗi, vui lòng cài đặt lại : (',\n  'engine-missing-message': 'Ứng dụng bị thiếu tập tin, vui lòng cài đặt lại : (',\n  'system-error-title': 'Lỗi hệ thống',\n  'system-error-message': 'Khởi động ứng dụng thất bại: {{message}}',\n  'hide': 'Ẩn Motrix',\n  'hide-others': 'Ẩn tất cả',\n  'unhide': 'Hiển thị tất cả',\n  'show': 'Hiển thị Motrix',\n  'quit': 'Thoát Motrix',\n  'under-development-message': 'Xin lỗi, tính năng này đang được phát triển...',\n  'yes': 'Có',\n  'no': 'Không',\n  'save': 'Lưu',\n  'reset': 'Loại bỏ',\n  'cancel': 'Huỷ',\n  'submit': 'Tải về',\n  'gt1d': '> 1 ngày',\n  'hour': ' giờ',\n  'minute': ' phút',\n  'second': ' giây'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/edit.js",
    "content": "export default {\n  'undo': 'Hoàn tác',\n  'redo': 'Redo',\n  'cut': 'Cắt',\n  'copy': 'Sao chép',\n  'paste': 'Dán',\n  'delete': 'Xóa',\n  'select-all': 'Chọn tất cả'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/help.js",
    "content": "export default {\n  'official-website': 'Trang web của Motrix',\n  'manual': 'Hướng dẫn sử dụng',\n  'release-notes': 'Ghi chú Phát hành...',\n  'report-problem': 'Báo cáo Vấn đề',\n  'toggle-dev-tools': 'Mở công cụ Dành cho Nhà phát triển'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/vi/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': 'Tập tin',\n  'task': 'Tác vụ',\n  'edit': 'Chỉnh sửa',\n  'window': 'Cửa sổ',\n  'help': 'Trợ giúp'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/preferences.js",
    "content": "export default {\n  'basic': 'Cơ bản',\n  'advanced': 'Nâng cao',\n  'lab': 'Phòng thí nghiệm',\n  'save': 'Lưu & Áp dụng',\n  'save-success-message': 'Lưu cài đặt thành công',\n  'save-fail-message': 'Lưu cài đặt thất bại',\n  'discard': 'Loại bỏ',\n  'startup': 'Khởi động',\n  'open-at-login': 'Mở khi đăng nhập',\n  'keep-window-state': 'Giữ kích thước và vị trí của cửa sổ khi thoát',\n  'auto-resume-all': 'Tự động tiếp tục tất cả các tác vụ chưa hoàn thành',\n  'default-dir': 'Đường dẫn mặc định',\n  'mas-default-dir-tips': 'Do các hạn chế cấp phép sandbox của App Store, thư mục tải xuống mặc định được khuyến nghị đặt tại ~/Downloads',\n  'transfer-settings': 'Tốc độ truyền',\n  'transfer-speed-upload': 'Giới hạn Tải lên',\n  'transfer-speed-download': 'Giới hạn Tải về',\n  'transfer-speed-unlimited': 'Không giới hạn',\n  'bt-settings': 'BitTorrent',\n  'bt-save-metadata': 'Lưu liên kết magnet dưới dạng tệp torrent',\n  'bt-auto-download-content': 'Tự động tải xuống nam châm và nội dung torrent',\n  'bt-force-encryption': 'Bắt BT mã hóa đầy đủ',\n  'keep-seeding': 'Tiếp tục seed cho đến khi dừng lại theo cách thủ công',\n  'seed-ratio': 'Tỷ lệ seed',\n  'seed-time': 'Thời gian seed',\n  'seed-time-unit': 'phút',\n  'task-manage': 'Quản lý Tác vụ',\n  'max-concurrent-downloads': 'Số tác vụ đang hoạt động tối đa',\n  'max-connection-per-server': 'Số kết nối tối đa trên mỗi máy chủ',\n  'new-task-show-downloading': 'Tự động hiển thị tải xuống sau khi thêm tác vụ',\n  'no-confirm-before-delete-task': 'Không cần xác nhận trước khi xóa tác vụ',\n  'continue': 'Tiếp tục',\n  'task-completed-notify': 'Thông báo sau khi tải xuống hoàn tất',\n  'auto-purge-record': 'Tự động xoá hồ sơ tải xuống khi thoát khỏi ứng dụng',\n  'ui': 'UI',\n  'appearance': 'Giao diện',\n  'theme-auto': 'Tự động',\n  'theme-light': 'Nền sáng',\n  'theme-dark': 'Nền tối',\n  'auto-hide-window': 'Tự động ẩn cửa sổ',\n  'run-mode': 'Chạy như',\n  'run-mode-standard': 'Ứng dụng Tiêu chuẩn',\n  'run-mode-tray': 'Ứng dụng khay thông báo',\n  'run-mode-hide-tray': 'Ẩn ứng dụng khay hệ thống',\n  'tray-speedometer': 'Thanh menu hiển thị tốc độ thời gian thực',\n  'show-progress-bar': 'Hiển thị thanh tiến trình tải xuống',\n  'language': 'Ngôn ngữ',\n  'change-language': 'Thay đổi Ngôn ngữ',\n  'hide-app-menu': 'Ẩn thanh Menu (Chỉ Windows & Linux)',\n  'proxy': 'Proxy',\n  'enable-proxy': 'Bật Proxy',\n  'proxy-bypass-input-tips': 'Bỏ qua cài đặt proxy cho các Máy chủ và tên miền này, mỗi cái một dòng',\n  'proxy-scope-download': 'Tải về',\n  'proxy-scope-update-app': 'Cập nhật ứng dụng',\n  'proxy-scope-update-trackers': 'Cập nhật theo dõi',\n  'proxy-tips': 'Xem Proxy Thủ Công',\n  'bt-tracker': 'Máy Chủ Tracker',\n  'bt-tracker-input-tips': 'Máy chủ theo dõi, mỗi thông tin trên một dòng',\n  'bt-tracker-tips': 'Khuyên dùng: ',\n  'sync-tracker-tips': 'Đồng bộ',\n  'auto-sync-tracker': 'Tự động cập nhật danh sách tracker mỗi ngày',\n  'port': 'Cổng giao tiếp cuối',\n  'bt-port': 'Cổng giao tiếp BT cuối',\n  'dht-port': 'Cổng giao tiếp DHT cuối',\n  'security': 'Bảo mật',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'Cổng Nghe RPC',\n  'rpc-secret': 'RPC bí mật',\n  'rpc-secret-tips': 'Xem RPC bí mật thủ công',\n  'developer': 'Lập trình viên',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': 'Mock User-Agent',\n  'aria2-conf-path': 'Đường dẫn aria2.conf tích hợp sẵn',\n  'app-log-path': 'Đường dẫn nhật ký ứng dụng',\n  'download-session-path': 'Đường dẫn phiên tải về',\n  'session-reset': 'Đặt lại phiên tải xuống',\n  'factory-reset': 'Khôi phục cài đặt gốc',\n  'factory-reset-confirm': 'Bạn có chắc chắn muốn khôi phục cài đặt gốc?',\n  'lab-warning': '⚠️ Kích hoạt các tính năng trong phòng thí nghiệm có thể dẫn đến sự cố ứng dụng hoặc mất dữ liệu, hãy cân nhắc cho quyết định của mình!',\n  'download-protocol': 'Các giao thức',\n  'protocols-default-client': 'Đặt làm máy khách mặc định cho các giao thức sau',\n  'protocols-magnet': 'Magnet [ magnet:// ]',\n  'protocols-thunder': 'Thunder [ thunder:// ]',\n  'browser-extensions': 'Tiện ích mở rộng',\n  'baidu-exporter': 'BaiduExporter',\n  'browser-extensions-tips': 'Được cung cấp bởi Cộng đồng, ',\n  'baidu-exporter-help': 'Nhấn vào đây để sử dụng',\n  'auto-update': 'Tự động cập nhật',\n  'auto-check-update': 'Tự động kiểm tra cập nhật',\n  'last-check-update-time': 'Kiểm tra cập nhật lần cuối',\n  'not-saved': 'Tùy chọn chưa được lưu',\n  'not-saved-confirm': 'Các tùy chọn đã sửa đổi sẽ bị mất, bạn có chắc chắn thoát không?'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/subnav.js",
    "content": "export default {\n  'task-list': 'Tác vụ',\n  'preferences': 'Cài đặt'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/task.js",
    "content": "export default {\n  'active': 'Đang tải về',\n  'waiting': 'Đang chờ',\n  'stopped': 'Ngừng',\n  'new-task': 'Tác vụ mới',\n  'new-bt-task': 'Tác vụ BT mới',\n  'open-file': 'Mở tệp Torrent...',\n  'uri-task': 'URL',\n  'torrent-task': 'Torrent',\n  'uri-task-tips': 'Mỗi URL tác vụ một dòng (hỗ trợ magnet)',\n  'thunder-link-tips': 'Mẹo: Liên kết Thunder có thể không tải được sau khi giải mã',\n  'new-task-uris-required': 'Vui lòng nhập ít nhất một url tài nguyên hợp lệ',\n  'new-task-torrent-required': 'Vui lòng chọn tệp Torrent',\n  'file-name': 'Tên',\n  'file-extension': 'Loại',\n  'file-size': 'Kích thước',\n  'file-completed-size': 'Đã hoàn thành',\n  'selected-files-sum': 'Đã chọn: {{selectedFilesCount}} tập tin, tổng kích thước {{selectedFilesTotalSize}}',\n  'select-at-least-one': 'Vui lòng chọn ít nhất một tệp',\n  'task-gid': 'GID',\n  'task-name': 'Tên tác vụ',\n  'task-out': 'Đổi tên',\n  'task-out-tips': 'Không bắt buộc',\n  'task-split': 'Chia nhỏ',\n  'task-dir': 'Lưu đến',\n  'pause-task': 'Tạm dừng Tác vụ',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Authorization',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': 'Lỗi',\n  'task-piece': 'Mảnh',\n  'task-piece-length': 'Kích thước mảnh',\n  'task-num-pieces': 'Các mảnh',\n  'task-bittorrent-info': 'Thông tin Torrent',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': 'Ngày tạo',\n  'task-bittorrent-comment': 'Bình luận',\n  'task-progress-info': 'Tiến trình',\n  'task-status': 'Trạng thái',\n  'task-num-seeders': 'Seeders',\n  'task-connections': 'Kết nối',\n  'task-file-size': 'Kích thước',\n  'task-download-speed': 'Tốc độ tải về',\n  'task-upload-speed': 'Tốc độ tải lên',\n  'task-download-length': 'Đã tải xuống',\n  'task-upload-length': 'Đã tải lên',\n  'task-ratio': 'Tỉ lệ',\n  'task-peer-host': 'Tổ chức',\n  'task-peer-ip': 'IP',\n  'task-peer-client': 'Clients',\n  'navigate-to-downloading': 'Điều hướng tải xuống',\n  'show-advanced-options': 'Lựa chọn Nâng cao',\n  'copyright-warning': 'Cảnh báo Bản quyền',\n  'copyright-warning-message': 'Tập tin bạn muốn tải xuống có thể là âm thanh hoặc video có bản quyền, vui lòng đảm bảo rằng bạn có quyền truy cập vào nó.',\n  'copyright-yes': 'Đúng, tôi được cấp phép',\n  'copyright-no': 'Không, tôi không được cấp phép',\n  'copyright-error-message': 'Không thể thêm Tác vụ vì vấn đề bản quyền',\n  'pause-task-success': 'Dừng tác vụ thành công \"{{taskName}}\"',\n  'pause-task-fail': 'Dừng tác vụ thất bại \"{{taskName}}\"',\n  'resume-task': 'Tiếp tục Tác vụ',\n  'resume-task-success': 'Tác vụ được tải lại thành công \"{{taskName}}\"',\n  'resume-task-fail': 'Tác vụ không thể tải lại \"{{taskName}}\"',\n  'delete-task': 'Xóa tác vụ',\n  'delete-selected-tasks': 'Xóa những tác vụ được chọn',\n  'delete-task-confirm': 'Bạn có chắc chắn muốn xóa tác vụ tải xuống \"{{taskName}}\"?',\n  'batch-delete-task-confirm': 'Bạn có chắc chắn muốn xóa {{Count}} tác vụ tải xuống không?',\n  'delete-task-label': 'Xóa kèm tập tin',\n  'delete-task-success': 'Xóa tác vụ thành công \"{{taskName}}\"',\n  'delete-task-fail': 'Xóa tác vụ thất bại \"{{taskName}}\"',\n  'remove-task-file-fail': 'Không thể xóa (các) tập tin trong tác vụ, vui lòng xóa chúng theo cách thủ công',\n  'remove-task-config-file-fail': 'Không thể xóa tập tin cấu hình tác vụ, vui lòng xóa chúng theo cách thủ công',\n  'move-task-up': 'Đưa tác vụ lên',\n  'move-task-down': 'Đưa tác vụ xuống',\n  'pause-all-task': 'Dừng Tất cả Tác vụ',\n  'pause-all-task-success': 'Dừng tất cả tác vụ thành công',\n  'pause-all-task-fail': 'Dừng tất cả tác vụ thất bại',\n  'resume-all-task': 'Tải lại tất cả tác vụ',\n  'resume-all-task-success': 'Tải lại tất cả tác vụ thành công',\n  'resume-all-task-fail': 'Tải lại tất cả tác vụ thất bại',\n  'select-all-task': 'Chọn tất cả tác vụ',\n  'clear-recent-tasks': 'Xóa các tác vụ gần đây',\n  'purge-record': 'Làm mới bản ghi tác vụ',\n  'purge-record-success': 'Làm mới bản ghi tác vụ thành công',\n  'purge-record-fail': 'Làm mới tản ghi tác vụ thất bại',\n  'batch-delete-task-success': 'Xóa các tác vụ hàng loạt thành công',\n  'batch-delete-task-fail': 'Xóa các tác vụ hàng loạt thất bại',\n  'refresh-list': 'Làm mới danh sách tác vụ',\n  'no-task': 'Hiện tại không có tác vụ',\n  'copy-link': 'Sao chép đường dẫn',\n  'copy-link-success': 'Sao chép đường dẫn thành công',\n  'remove-record': 'Xóa bản ghi tác vụ',\n  'remove-record-confirm': 'Bạn có chắc chắn muốn xóa bản ghi tải xuống cho \"{{taskName}}\" không?',\n  'remove-record-label': 'Xóa kèm tập tin',\n  'remove-record-success': 'Xoá thành công bản ghi tác vụ cho \"{{taskName}}\"',\n  'remove-record-fail': 'Xoá thất bại bản ghi tác vụ cho \"{{taskName}}\"',\n  'show-in-folder': 'Hiển thị tác vụ trong thư mục',\n  'file-not-exist': 'Tập tin mục tiêu không tồn tại hoặc đã bị xóa',\n  'file-path-error': 'Lỗi đường dẫn tệp',\n  'opening-task-message': 'Đang mở \"{{taskName}}\" ...',\n  'get-task-name': 'Lấy tên tác vụ....',\n  'remaining-prefix': 'Còn lại',\n  'select-torrent': 'Kéo thả tệp torrent vào đây hoặc nhấp để chọn',\n  'task-detail-title': 'Thông tin tác vụ',\n  'task-info-dialog-title': '{{title}} Chi tiết',\n  'download-start-message': 'Bắt đầu tải xuống {{taskName}}',\n  'download-pause-message': 'Tạm dừng tải xuống {{taskName}}',\n  'download-stop-message': 'Đã dừng tải xuống {{taskName}}',\n  'download-error-message': 'Xảy ra lỗi khi tải xuống {{taskName}}',\n  'download-complete-message': 'Đã hoàn tất tải xuống {{taskName}}',\n  'download-complete-notify': 'Tải xuống hoàn tất',\n  'bt-download-complete-message': 'Đã hoàn tất tải xuống {{taskName}}, đang seed',\n  'bt-download-complete-notify': 'BT đã hoàn tất tải xuống, đang seed...',\n  'bt-download-complete-tips': 'Mẹo: Bạn có thể dừng một tác vụ để kết thúc việc seed',\n  'bt-stopping-seeding-tip': 'Ngừng seed, sẽ mất một thời gian để ngắt kết nối, vui lòng đợi...',\n  'download-fail-message': 'Không thể tải xuống {{taskName}}',\n  'download-fail-notify': 'Tải xuống thất bại'\n}\n"
  },
  {
    "path": "src/shared/locales/vi/window.js",
    "content": "export default {\n  'reload': 'Tải lại',\n  'close': 'Đóng',\n  'minimize': 'Thu nhỏ',\n  'zoom': 'Thu phóng',\n  'toggle-fullscreen': 'Mở Toàn Màn Hình',\n  'front': 'Đưa tất cả lên phía trước'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/about.js",
    "content": "export default {\n  'engine-version': '引擎版本',\n  'license': '开源许可',\n  'about': '关于我们',\n  'release': '更新日志',\n  'support': '帮助支持'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/app.js",
    "content": "export default {\n  'task-list': '任务列表',\n  'add-task': '新建任务',\n  'about': '关于 Motrix',\n  'preferences': '偏好设置...',\n  'check-for-updates': '检查更新...',\n  'check-updates-now': '立即检查',\n  'checking-for-updates': '正在检查更新...',\n  'check-for-updates-title': '检查更新',\n  'update-available-message': '发现新版本，是否现在更新？',\n  'update-not-available-message': '已是最新版',\n  'update-downloaded-message': '更新下载完成，应用程序将退出并开始更新...',\n  'update-error-message': '检查更新失败',\n  'engine-damaged-message': '引擎损坏，请重新安装 : (',\n  'engine-missing-message': '引擎缺失，请重新安装 : (',\n  'system-error-title': '系统错误',\n  'system-error-message': '应用启动失败: {{message}}',\n  'hide': '隐藏 Motrix',\n  'hide-others': '隐藏其他',\n  'unhide': '显示全部',\n  'show': '显示 Motrix',\n  'quit': '退出 Motrix',\n  'under-development-message': '该功能开发中...',\n  'yes': '是',\n  'no': '否',\n  'save': '保存',\n  'reset': '放弃',\n  'cancel': '取 消',\n  'submit': '提 交',\n  'gt1d': '超过一天',\n  'hour': '时',\n  'minute': '分',\n  'second': '秒'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/edit.js",
    "content": "export default {\n  'undo': '撤销',\n  'redo': '重做',\n  'cut': '剪切',\n  'copy': '复制',\n  'paste': '黏贴',\n  'delete': '删除',\n  'select-all': '全选'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/help.js",
    "content": "export default {\n  'official-website': 'Motrix 官网',\n  'manual': '使用手册',\n  'release-notes': '发行说明...',\n  'report-problem': '报告问题',\n  'toggle-dev-tools': '开发者工具'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': '文件',\n  'task': '任务',\n  'edit': '编辑',\n  'window': '窗口',\n  'help': '帮助'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/preferences.js",
    "content": "export default {\n  'basic': '基础设置',\n  'advanced': '进阶设置',\n  'lab': '实验室',\n  'save': '保存并应用',\n  'save-success-message': '偏好设置保存成功',\n  'save-fail-message': '偏好设置保存失败',\n  'discard': '放弃',\n  'startup': '启动',\n  'open-at-login': '开机自动启动',\n  'keep-window-state': '恢复上次退出时窗口的大小和位置',\n  'auto-resume-all': '自动开始未完成的任务',\n  'default-dir': '默认下载路径',\n  'mas-default-dir-tips': '因 App Store 的沙箱权限限制，默认下载路径建议设置为您的「下载」目录',\n  'transfer-settings': '传输设置',\n  'transfer-speed-upload': '上传限速',\n  'transfer-speed-download': '下载限速',\n  'transfer-speed-unlimited': '不限速',\n  'bt-settings': 'BT 设置',\n  'bt-save-metadata': '保存磁力链接元数据为种子文件',\n  'bt-auto-download-content': '自动开始下载磁力链接、种子的文件',\n  'bt-force-encryption': 'BT强制加密',\n  'keep-seeding': '持续做种，直到手动停止',\n  'seed-ratio': '做种分享率',\n  'seed-time': '做种时间',\n  'seed-time-unit': '分钟',\n  'task-manage': '任务管理',\n  'max-concurrent-downloads': '同时下载的最大任务数',\n  'max-connection-per-server': '每个服务器最大连接数',\n  'new-task-show-downloading': '新建任务后自动跳转到下载页面',\n  'no-confirm-before-delete-task': '删除任务前无需确认',\n  'continue': '断点续传',\n  'task-completed-notify': '下载完成后通知',\n  'auto-purge-record': '当应用退出时自动清除下载记录',\n  'ui': '界面',\n  'appearance': '外观',\n  'theme-auto': '自动',\n  'theme-light': '浅色',\n  'theme-dark': '深色',\n  'auto-hide-window': '自动隐藏窗口',\n  'run-mode': '运行为',\n  'run-mode-standard': '标准应用',\n  'run-mode-tray': '托盘应用',\n  'run-mode-hide-tray': '隐藏托盘应用',\n  'tray-speedometer': '托盘显示实时速度',\n  'show-progress-bar': '显示下载进度条',\n  'language': '语言',\n  'change-language': '切换语言',\n  'hide-app-menu': '隐藏菜单栏（仅支持 Windows 和 Linux）',\n  'proxy': '代理',\n  'enable-proxy': '使用代理服务器',\n  'proxy-bypass-input-tips': '忽略这些主机与域的代理设置，一行一个',\n  'proxy-scope-download': '下载',\n  'proxy-scope-update-app': '更新应用程序',\n  'proxy-scope-update-trackers': '更新 Tracker 列表',\n  'proxy-tips': '查看代理配置说明',\n  'bt-tracker': 'Tracker 服务器',\n  'bt-tracker-input-tips': 'Tracker 服务器，一行一个',\n  'bt-tracker-tips': '推荐使用：',\n  'sync-tracker-tips': '从服务器同步',\n  'auto-sync-tracker': '每天自动更新 Tracker 服务器列表',\n  'port': '监听端口',\n  'bt-port': 'BT 监听端口',\n  'dht-port': 'DHT 监听端口',\n  'security': '安全性',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC 监听端口',\n  'rpc-secret': 'RPC 授权密钥',\n  'rpc-secret-tips': '查看说明文档',\n  'developer': '开发者',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': '模拟用户代理（UA）',\n  'aria2-conf-path': '内置的 aria2.conf 路径',\n  'app-log-path': '应用日志路径',\n  'download-session-path': '下载会话路径',\n  'session-reset': '重置下载会话记录',\n  'session-reset-confirm': '你确定要重置下载会话记录吗?',\n  'factory-reset': '恢复初始设置',\n  'factory-reset-confirm': '你确定要恢复为初始设置吗?',\n  'lab-warning': '⚠️启用实验特性可能造成应用崩溃或数据丢失，请自行决定！',\n  'download-protocol': '下载协议',\n  'protocols-default-client': '设置为以下协议的默认客户端',\n  'protocols-magnet': '磁力链接 [ magnet:// ]',\n  'protocols-thunder': '迅雷链接 [ thunder:// ]',\n  'browser-extensions': '浏览器扩展',\n  'baidu-exporter': '百度网盘助手',\n  'browser-extensions-tips': '社区提供的浏览器扩展「不保证可用性」，',\n  'baidu-exporter-help': '点此查看使用说明',\n  'auto-update': '自动更新',\n  'auto-check-update': '自动检查更新',\n  'last-check-update-time': '上次检查更新时间',\n  'follow-metalink': '自动开始下载磁力链接、种子内的文件',\n  'follow-torrent': '种子下载完后自动下载种子内容',\n  'not-saved': '设置未保存',\n  'not-saved-confirm': '已修改的设置将会丢失，确定要离开吗?'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/subnav.js",
    "content": "export default {\n  'task-list': '任务列表',\n  'preferences': '偏好设置'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/task.js",
    "content": "export default {\n  'active': '下载中',\n  'waiting': '等待中',\n  'stopped': '已停止',\n  'new-task': '新建任务',\n  'new-bt-task': '新建 BT 任务',\n  'open-file': '打开种子文件...',\n  'uri-task': '链接任务',\n  'torrent-task': '种子任务',\n  'uri-task-tips': '添加多个下载链接时，请确保每行只有一个链接（支持磁力链）',\n  'thunder-link-tips': '友情提示：迅雷链接解码之后的资源不一定存在',\n  'new-task-uris-required': '请至少输入一个有效的下载地址',\n  'new-task-torrent-required': '请先选择种子文件',\n  'file-name': '文件名',\n  'file-extension': '扩展名',\n  'file-size': '大小',\n  'file-completed-size': '已完成',\n  'selected-files-sum': '已选：{{selectedFilesCount}}个文件，共 {{selectedFilesTotalSize}}',\n  'select-at-least-one': '请选择至少一个文件',\n  'task-gid': 'GID',\n  'task-name': '任务名称',\n  'task-out': '重命名',\n  'task-out-tips': '选填',\n  'task-split': '分片数',\n  'task-dir': '存储路径',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Authorization',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': '代理',\n  'task-error-info': '错误信息',\n  'task-piece': '分片',\n  'task-piece-length': '分片大小',\n  'task-num-pieces': '分片数量',\n  'task-bittorrent-info': '种子信息',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': '发布时间',\n  'task-bittorrent-comment': '备注',\n  'task-progress-info': '任务进度',\n  'task-status': '任务状态',\n  'task-num-seeders': '种子数',\n  'task-connections': '连接数',\n  'task-file-size': '文件大小',\n  'task-download-speed': '下载速度',\n  'task-upload-speed': '上传速度',\n  'task-download-length': '已下载',\n  'task-upload-length': '已上传',\n  'task-ratio': '分享率',\n  'task-peer-host': '服务器',\n  'task-peer-ip': 'IP',\n  'task-peer-client': '客户端',\n  'navigate-to-downloading': '跳转到下载页面',\n  'show-advanced-options': '高级选项',\n  'copyright-warning': '版权提醒',\n  'copyright-warning-message': '您要下载的文件可能是有版权的音视频，请确保您有相应的版权方授权。',\n  'copyright-yes': '是，我有版权方授权',\n  'copyright-no': '否',\n  'copyright-error-message': '因版权问题，添加任务失败',\n  'pause-task': '暂停任务',\n  'pause-task-success': '暂停任务 \"{{taskName}}\" 成功',\n  'pause-task-fail': '暂停任务 \"{{taskName}}\" 失败',\n  'resume-task': '恢复任务',\n  'resume-task-success': '恢复任务 \"{{taskName}}\" 成功',\n  'resume-task-fail': '恢复任务 \"{{taskName}}\" 失败',\n  'delete-task': '移除任务',\n  'delete-selected-tasks': '移除选中的任务',\n  'delete-task-confirm': '你确定要移除 \"{{taskName}}\" 下载任务吗?',\n  'batch-delete-task-confirm': '你确定要批量移除{{count}}个下载任务吗?',\n  'delete-task-label': '同时删除文件',\n  'delete-task-success': '移除任务 \"{{taskName}}\" 成功',\n  'delete-task-fail': '移除任务 \"{{taskName}}\" 失败',\n  'remove-task-file-fail': '删除任务文件失败，请手动删除',\n  'remove-task-config-file-fail': '删除任务配置文件失败，请手动删除',\n  'move-task-up': '上移任务',\n  'move-task-down': '下移任务',\n  'pause-all-task': '暂停所有任务',\n  'pause-all-task-success': '暂停所有任务成功',\n  'pause-all-task-fail': '暂停所有任务失败',\n  'resume-all-task': '恢复所有任务',\n  'resume-all-task-success': '恢复所有任务成功',\n  'resume-all-task-fail': '恢复所有任务失败',\n  'select-all-task': '选中所有任务',\n  'clear-recent-tasks': '清除最近的下载记录',\n  'purge-record': '清除下载记录',\n  'purge-record-success': '清除下载记录成功',\n  'purge-record-fail': '清除下载记录失败',\n  'batch-delete-task-success': '批量移除任务成功',\n  'batch-delete-task-fail': '批量移除任务失败',\n  'refresh-list': '刷新任务列表',\n  'no-task': '当前没有下载任务',\n  'copy-link': '拷贝链接',\n  'copy-link-success': '拷贝链接成功',\n  'remove-record': '移除下载记录',\n  'remove-record-confirm': '你确定要移除 \"{{taskName}}\" 下载记录吗?',\n  'remove-record-label': '同时删除文件',\n  'remove-record-success': '移除 \"{{taskName}}\" 下载记录成功',\n  'remove-record-fail': '移除 \"{{taskName}}\" 下载记录失败',\n  'show-in-folder': '在文件夹中显示',\n  'file-not-exist': '目标文件不存在或已删除',\n  'file-path-error': '文件路径异常',\n  'opening-task-message': '正在打开 \"{{taskName}}\" ...',\n  'get-task-name': '获取任务名中...',\n  'remaining-prefix': '剩余',\n  'select-torrent': '将种子拖到此处，或点击选择',\n  'task-detail-title': '任务详情',\n  'task-info-dialog-title': '{{title}} 详情',\n  'download-start-message': '开始下载 {{taskName}}',\n  'download-pause-message': '暂停下载 {{taskName}}',\n  'download-stop-message': '{{taskName}} 下载中止',\n  'download-error-message': '{{taskName}} 下载发生错误',\n  'download-complete-message': '{{taskName}} 下载完成',\n  'download-complete-notify': '下载完成',\n  'bt-download-complete-message': '{{taskName}} 下载完成，正在做种...',\n  'bt-download-complete-notify': 'BT 任务下载完成，正在做种...',\n  'bt-download-complete-tips': '提示：你可以停止任务结束做种',\n  'bt-stopping-seeding-tip': '正在停止做种，断开连接需要些时间，请耐心等待...',\n  'download-fail-message': '{{taskName}} 下载失败',\n  'download-fail-notify': '下载失败'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-CN/window.js",
    "content": "export default {\n  'reload': '重新加载',\n  'close': '关闭',\n  'minimize': '最小化',\n  'zoom': '放大',\n  'toggle-fullscreen': '进入全屏幕',\n  'front': '前置全部窗口'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/about.js",
    "content": "export default {\n  'engine-version': '引擎版本',\n  'license': '授權條款',\n  'about': '關於',\n  'release': '版本資訊',\n  'support': '支援'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/app.js",
    "content": "export default {\n  'task-list': '任務列表',\n  'add-task': '新增任務',\n  'about': '關於 Motrix',\n  'preferences': '偏好設定...',\n  'check-for-updates': '檢查更新...',\n  'check-updates-now': '立即檢查',\n  'checking-for-updates': '正在檢查更新...',\n  'check-for-updates-title': '檢查更新',\n  'update-available-message': '發現新版本，是否現在更新？',\n  'update-not-available-message': '已是最新版',\n  'update-downloaded-message': '更新下載完成，應用程式將退出並開始更新...',\n  'update-error-message': '檢查更新失敗',\n  'engine-damaged-message': '引擎損壞，請重新安裝 : (',\n  'engine-missing-message': '引擎缺失，請重新安裝 : (',\n  'system-error-title': '系統錯誤',\n  'system-error-message': '應用程式啟動失敗: {{message}}',\n  'hide': '隱藏 Motrix',\n  'hide-others': '隱藏其它',\n  'unhide': '顯示全部',\n  'show': '顯示 Motrix',\n  'quit': '結束 Motrix',\n  'under-development-message': '該功能開發中...',\n  'yes': '是',\n  'no': '否',\n  'save': '儲存',\n  'reset': '捨棄',\n  'cancel': '取消',\n  'submit': '送出',\n  'gt1d': '超過一天',\n  'hour': '時',\n  'minute': '分',\n  'second': '秒'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/edit.js",
    "content": "export default {\n  'undo': '還原',\n  'redo': '取消還原',\n  'cut': '剪下',\n  'copy': '複製',\n  'paste': '貼上',\n  'delete': '刪除',\n  'select-all': '全選'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/help.js",
    "content": "export default {\n  'official-website': 'Motrix 官網',\n  'manual': '使用說明',\n  'release-notes': '版本資訊...',\n  'report-problem': '回報問題',\n  'toggle-dev-tools': '開發者工具'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/index.js",
    "content": "import about from './about'\nimport app from './app'\nimport edit from './edit'\nimport help from './help'\nimport menu from './menu'\nimport preferences from './preferences'\nimport subnav from './subnav'\nimport task from './task'\nimport window from './window'\n\nexport default {\n  about,\n  app,\n  edit,\n  help,\n  menu,\n  preferences,\n  subnav,\n  task,\n  window\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/menu.js",
    "content": "export default {\n  'app': 'Motrix',\n  'file': '檔案',\n  'task': '任務',\n  'edit': '編輯',\n  'window': '視窗',\n  'help': '說明'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/preferences.js",
    "content": "export default {\n  'basic': '基本設定',\n  'advanced': '進階設定',\n  'lab': '實驗性功能',\n  'save': '儲存並套用',\n  'save-success-message': '偏好設定儲存成功',\n  'save-fail-message': '偏好設定儲存失敗',\n  'discard': '捨棄',\n  'startup': '啟動',\n  'open-at-login': '開機自動啟動',\n  'keep-window-state': '恢復上次退出時視窗的大小和位置',\n  'auto-resume-all': '自動繼續未完成的任務',\n  'default-dir': '預設下載路徑',\n  'mas-default-dir-tips': '因 App Store 的沙盒權限限制，建議將預設下載路徑設定為您的「下載」資料夾',\n  'transfer-settings': '傳輸設定',\n  'transfer-speed-upload': '上傳限制',\n  'transfer-speed-download': '下載限制',\n  'transfer-speed-unlimited': '無限制',\n  'bt-settings': 'BT 設定',\n  'bt-save-metadata': '將磁力連結中繼資料儲存為種子檔案',\n  'bt-auto-download-content': '自動開始下載磁力連結、種子的檔案',\n  'bt-force-encryption': '強制 BT 加密',\n  'keep-seeding': '持續做種，直到手動停止',\n  'seed-ratio': '做種分享率',\n  'seed-time': '做種時間',\n  'seed-time-unit': '分鐘',\n  'task-manage': '任務管理',\n  'max-concurrent-downloads': '最多可同時下載的任務數',\n  'max-connection-per-server': '每台伺服器的最大連線數',\n  'new-task-show-downloading': '新增任務後自動顯示下載頁面',\n  'no-confirm-before-delete-task': '刪除任務之前無需確認',\n  'continue': '斷點續傳',\n  'task-completed-notify': '下載完成後通知',\n  'auto-purge-record': '當結束程式時自動清除下載紀錄',\n  'ui': '使用者介面',\n  'appearance': '外觀',\n  'theme-auto': '自動',\n  'theme-light': '淺色',\n  'theme-dark': '深色',\n  'auto-hide-window': '自動隱藏視窗',\n  'run-mode': '運作模式', //macOS only feature\n  'run-mode-standard': '標準應用程式',\n  'run-mode-tray': '托盤應用程式',\n  'run-mode-hide-tray': '隱藏托盤應用程式',\n  'tray-speedometer': '托盤顯示即時速度',\n  'show-progress-bar': '顯示下載進度條',\n  'language': '語言',\n  'change-language': '更改語言',\n  'hide-app-menu': '隱藏選單列（僅支援 Windows 和 Linux）',\n  'proxy': 'Proxy',\n  'enable-proxy': '使用 Proxy',\n  'proxy-bypass-input-tips': '忽略這些主機與網域的 Proxy 設定，一列一個',\n  'proxy-scope-download': '下載',\n  'proxy-scope-update-app': '更新應用程式',\n  'proxy-scope-update-trackers': '更新 Tracker 列表',\n  'proxy-tips': '查看 Proxy 配置手冊',\n  'bt-tracker': 'Tracker 伺服器',\n  'bt-tracker-input-tips': 'Tracker 伺服器，一列一個',\n  'bt-tracker-tips': '推薦使用：',\n  'sync-tracker-tips': '從源伺服器同步',\n  'auto-sync-tracker': '每天自動更新 Tracker 伺服器列表',\n  'port': '監聽連接埠',\n  'bt-port': 'BT 監聽連接埠',\n  'dht-port': 'DHT 監聽連接埠',\n  'security': '安全性',\n  'rpc': 'RPC',\n  'rpc-listen-port': 'RPC 監聽埠',\n  'rpc-secret': 'RPC 授權密鑰',\n  'rpc-secret-tips': '查看說明手冊',\n  'developer': '開發者',\n  'user-agent': 'User-Agent',\n  'mock-user-agent': '偽裝 User Agent',\n  'aria2-conf-path': '內建的 aria2.conf 路徑',\n  'app-log-path': '應用程式記錄檔位置',\n  'download-session-path': '下載工作階段路徑',\n  'session-reset': '重設下載工作階段',\n  'session-reset-confirm': '您確定要重設下載工作階段嗎？',\n  'factory-reset': '還原出廠預設值',\n  'factory-reset-confirm': '您確定要還原為出廠預設值嗎？',\n  'lab-warning': '⚠️開啟實驗性功能可能會造成程式當機或資料遺失，請自行斟酌！',\n  'download-protocol': '下載協定',\n  'protocols-default-client': '設定為以下通訊協定的預設客戶端',\n  'protocols-magnet': '磁力連結 [ magnet:// ]',\n  'protocols-thunder': '迅雷連結 [ thunder:// ]',\n  'browser-extensions': '瀏覽器擴充功能',\n  'baidu-exporter': '百度網盤助手',\n  'browser-extensions-tips': '社群提供的瀏覽器擴充功能「不保證可用性」，',\n  'baidu-exporter-help': '點此檢視使用說明',\n  'auto-update': '自動更新',\n  'auto-check-update': '自動檢查更新',\n  'last-check-update-time': '上次檢查更新時間',\n  'not-saved': '設定未儲存',\n  'not-saved-confirm': '已修改的設定將會丟失，確定要離開嗎？'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/subnav.js",
    "content": "export default {\n  'task-list': '任務清單',\n  'preferences': '偏好設定'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/task.js",
    "content": "export default {\n  'active': '下載中',\n  'waiting': '等待中',\n  'stopped': '已停止',\n  'new-task': '新增任務',\n  'new-bt-task': '新增 BT 任務',\n  'open-file': '開啟種子檔案...',\n  'uri-task': '連結任務',\n  'torrent-task': '種子任務',\n  'uri-task-tips': '新增多個下載連結時，請確保每行只有一個連結（支援磁力連結）',\n  'thunder-link-tips': '提醒：迅雷連結解碼之後的資源不一定存在',\n  'new-task-uris-required': '請至少輸入一個有效的資源網址',\n  'new-task-torrent-required': '請先選擇種子檔案',\n  'file-name': '檔案名稱',\n  'file-extension': '副檔名',\n  'file-size': '大小',\n  'file-completed-size': '已下載',\n  'selected-files-sum': '已選取：{{selectedFilesCount}}個檔案，總計 {{selectedFilesTotalSize}}',\n  'select-at-least-one': '請選擇至少一個檔案',\n  'task-gid': 'GID',\n  'task-name': '任務名稱',\n  'task-out': '重新命名',\n  'task-out-tips': '選填',\n  'task-split': '分片數',\n  'task-dir': '儲存資料夾',\n  'task-ua': 'UA',\n  'task-user-agent': 'User-Agent',\n  'task-authorization': 'Authorization',\n  'task-referer': 'Referer',\n  'task-cookie': 'Cookie',\n  'task-proxy': 'Proxy',\n  'task-error-info': '錯誤訊息',\n  'task-piece': '分片',\n  'task-piece-length': '分片大小',\n  'task-num-pieces': '分片數量',\n  'task-bittorrent-info': '種子資訊',\n  'task-info-hash': 'Hash',\n  'task-bittorrent-creation-date': '發佈時間',\n  'task-bittorrent-comment': '備註',\n  'task-progress-info': '任務進度',\n  'task-status': '任務狀態',\n  'task-num-seeders': '種子數',\n  'task-connections': '連接數',\n  'task-file-size': '檔案大小',\n  'task-download-speed': '下載速度',\n  'task-upload-speed': '上傳速度',\n  'task-download-length': '已下載',\n  'task-upload-length': '已上傳',\n  'task-ratio': '分享率',\n  'task-peer-host': '伺服器',\n  'task-peer-ip': 'IP',\n  'task-peer-client': '客戶端',\n  'navigate-to-downloading': '前往下載頁面',\n  'show-advanced-options': '進階選項',\n  'copyright-warning': '版權警告',\n  'copyright-warning-message': '您要下載的檔案可能是有版權的音訊視訊，請確保您有相應的版權方授權。',\n  'copyright-yes': '是，我有版權方授權',\n  'copyright-no': '否',\n  'copyright-error-message': '因版權問題，新增任務失敗',\n  'pause-task': '暫停任務',\n  'pause-task-success': '暫停任務 \"{{taskName}}\" 成功',\n  'pause-task-fail': '暫停任務 \"{{taskName}}\" 失敗',\n  'resume-task': '繼續任務',\n  'resume-task-success': '繼續任務 \"{{taskName}}\" 成功',\n  'resume-task-fail': '繼續任務 \"{{taskName}}\" 失敗',\n  'delete-task': '移除任務',\n  'delete-selected-tasks': '移除選取的任務',\n  'delete-task-confirm': '你確定要移除 \"{{taskName}}\" 下載任務嗎？',\n  'batch-delete-task-confirm': '你確定要批量移除{{count}}個下載任務嗎？',\n  'delete-task-label': '同時刪除檔案',\n  'delete-task-success': '移除任務 \"{{taskName}}\" 成功',\n  'delete-task-fail': '移除任務 \"{{taskName}}\" 失敗',\n  'remove-task-file-fail': '刪除任務檔案失敗，請手動刪除',\n  'remove-task-config-file-fail': '刪除任務設定檔失敗，請手動刪除',\n  'move-task-up': '上移任務',\n  'move-task-down': '下移任務',\n  'pause-all-task': '暫停所有任務',\n  'pause-all-task-success': '暫停所有任務成功',\n  'pause-all-task-fail': '暫停所有任務失敗',\n  'resume-all-task': '繼續所有任務',\n  'resume-all-task-success': '繼續所有任務成功',\n  'resume-all-task-fail': '繼續所有任務失敗',\n  'select-all-task': '選擇所有任務',\n  'clear-recent-tasks': '清除最近的下載紀錄',\n  'purge-record': '清除下載紀錄',\n  'purge-record-success': '清除下載紀錄成功',\n  'purge-record-fail': '清除下載紀錄失敗',\n  'batch-delete-task-success': '批次刪除任務成功',\n  'batch-delete-task-fail': '批次刪除任務失敗',\n  'refresh-list': '重新整理任務清單',\n  'no-task': '目前没有下載任務',\n  'copy-link': '複製連結',\n  'copy-link-success': '複製連結成功',\n  'remove-record': '移除下載紀錄',\n  'remove-record-confirm': '確定要移除 \"{{taskName}}\" 下載紀錄嗎？',\n  'remove-record-label': '同時刪除檔案',\n  'remove-record-success': '移除 \"{{taskName}}\" 下載紀錄成功',\n  'remove-record-fail': '移除 \"{{taskName}}\" 下載紀錄失敗',\n  'show-in-folder': '在資料夾中顯示',\n  'file-not-exist': '目標檔案不存在或已刪除',\n  'file-path-error': '檔案路徑錯誤',\n  'opening-task-message': '正在打開 \"{{taskName}}\" ...',\n  'get-task-name': '取得任務名稱中...',\n  'remaining-prefix': '剩下',\n  'select-torrent': '將種子拖曳至此，或點選來選取',\n  'task-detail-title': '任務詳細資訊',\n  'task-info-dialog-title': '{{title}} 詳細資訊',\n  'download-start-message': '開始下載 {{taskName}}',\n  'download-pause-message': '暫停下載 {{taskName}}',\n  'download-stop-message': '{{taskName}} 下載中止',\n  'download-error-message': '{{taskName}} 下載錯誤',\n  'download-complete-message': '{{taskName}} 下載完成',\n  'download-complete-notify': '下載完成',\n  'bt-download-complete-message': '{{taskName}} 下載完成，正在做種...',\n  'bt-download-complete-notify': 'BT 任務下載完成，正在做種...',\n  'bt-download-complete-tips': '提示：你可以停止任務結束做種',\n  'bt-stopping-seeding-tip': '停止做種中，需要些時間才能斷開連接，請稍候...',\n  'download-fail-message': '{{taskName}} 下載失敗',\n  'download-fail-notify': '下載失敗'\n}\n"
  },
  {
    "path": "src/shared/locales/zh-TW/window.js",
    "content": "export default {\n  'reload': '重新載入',\n  'close': '關閉',\n  'minimize': '最小化',\n  'zoom': '放大',\n  'toggle-fullscreen': '切換全螢幕',\n  'front': '將全部視窗移至最前方'\n}\n"
  },
  {
    "path": "src/shared/ua.js",
    "content": "export const ARIA2_UA = 'aria2/1.36.0'\nexport const TRANSMISSION_UA = 'Transmission/3.00'\nexport const CHROME_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'\nexport const DU_UA = 'netdisk;6.0.0.12;PC;PC-Windows;10.0.16299;WindowsBaiduYunGuanJia'\n\nexport default {\n  aria2: ARIA2_UA,\n  transmission: TRANSMISSION_UA,\n  chrome: CHROME_UA,\n  du: DU_UA\n}\n"
  },
  {
    "path": "src/shared/utils/curl.js",
    "content": "import * as curlParser from '@bany/curl-to-json'\n\nexport const buildUrisFromCurl = (uris = []) => {\n  return uris.map((uri) => {\n    if (uri.startsWith('curl')) {\n      const parsedUri = curlParser(uri)\n      uri = parsedUri.url\n      if (parsedUri.params && Object.keys(parsedUri.params).length > 0) {\n        const paramsStr = Object.keys(parsedUri.params)\n          .map((k) => `${k}=${parsedUri.params[k]}`)\n          .join('&')\n        uri = `${uri}?${paramsStr}`\n      }\n      return uri\n    } else {\n      return uri\n    }\n  })\n}\n\nexport const buildHeadersFromCurl = (uris = []) => {\n  return uris.map((uri) => {\n    if (uri.startsWith('curl')) {\n      const parsed = curlParser(uri)\n      const header = parsed.header ?? {}\n      if (parsed.cookie) {\n        header.cookie = parsed.cookie\n      }\n      if (parsed['user-agent']) {\n        header['user-agent'] = parsed['user-agent']\n      }\n      if (parsed.referer) {\n        header.referer = parsed.referer\n      }\n      return header\n    } else {\n      return undefined\n    }\n  })\n}\n\nexport const buildDefaultOptionsFromCurl = (form, headers = []) => {\n  const firstNonNullHeader = headers.find((elem) => elem)\n  if (firstNonNullHeader) {\n    form.cookie = !form.cookie && firstNonNullHeader.cookie ? firstNonNullHeader.cookie : form.cookie\n    form.referer = !form.referer && firstNonNullHeader.referer ? firstNonNullHeader.referer : form.referer\n    form.userAgent = !form.userAgent && firstNonNullHeader['user-agent'] ? firstNonNullHeader['user-agent'] : form.userAgent\n    form.authorization = !form.authorization && firstNonNullHeader.authorization ? firstNonNullHeader.authorization : form.authorization\n  }\n  return form\n}\n"
  },
  {
    "path": "src/shared/utils/index.js",
    "content": "import {\n  camelCase,\n  compact,\n  difference,\n  isArray,\n  isEmpty,\n  isFunction,\n  isNaN,\n  isPlainObject,\n  kebabCase,\n  omitBy,\n  parseInt,\n  pick\n} from 'lodash'\nimport bitTorrentPeerId from 'bittorrent-peerid'\n\nimport { userKeys, systemKeys, needRestartKeys } from '@shared/configKeys'\nimport {\n  APP_THEME,\n  ENGINE_RPC_HOST,\n  GRAPHIC,\n  NONE_SELECTED_FILES,\n  SELECTED_ALL_FILES,\n  RESOURCE_TAGS,\n  IMAGE_SUFFIXES,\n  AUDIO_SUFFIXES,\n  VIDEO_SUFFIXES,\n  SUB_SUFFIXES,\n  UNKNOWN_PEERID,\n  SUPPORT_RTL_LOCALES,\n  UNKNOWN_PEERID_NAME,\n  DOCUMENT_SUFFIXES\n} from '@shared/constants'\n\nexport const bytesToSize = (bytes, precision = 1) => {\n  const b = parseInt(bytes, 10)\n  const sizes = ['B', 'KB', 'MB', 'GB', 'TB']\n  if (b === 0) { return '0 KB' }\n  const i = parseInt(Math.floor(Math.log(b) / Math.log(1024)), 10)\n  if (i === 0) { return `${b} ${sizes[i]}` }\n  return `${(b / (1024 ** i)).toFixed(precision)} ${sizes[i]}`\n}\n\nexport const extractSpeedUnit = (speed = '') => {\n  if (parseInt(speed) === 0) {\n    return 'K'\n  }\n\n  const regex = /^(\\d+\\.?\\d*)([KMG])$/\n  const match = regex.exec(speed)\n\n  if (!match) {\n    return 'K'\n  }\n\n  return match[2]\n}\n\nexport const bitfieldToPercent = (text) => {\n  const len = text.length - 1\n  let p\n  let one = 0\n  for (let i = 0; i < len; i++) {\n    p = parseInt(text[i], 16)\n    for (let j = 0; j < 4; j++) {\n      one += (p & 1)\n      p >>= 1\n    }\n  }\n  return Math.floor(one / (4 * len) * 100).toString()\n}\n\nexport const bitfieldToGraphic = (text) => {\n  const len = text.length\n  let result = ''\n  for (let i = 0; i < len; i++) {\n    result += GRAPHIC[Math.floor(parseInt(text[i], 16) / 4)] + ' '\n  }\n  return result\n}\n\nexport const peerIdParser = (str) => {\n  if (!str || str === UNKNOWN_PEERID) {\n    return UNKNOWN_PEERID_NAME\n  }\n\n  let parsed = {}\n  let decodedStr\n  try {\n    // decodeURI or decodeURIComponent cannot parse '%2DUT360W%2D%92%B6%EBh%1F%A1%DBfo%F6%D5I'\n    decodedStr = unescape(str)\n    const buffer = Buffer.from(decodedStr, 'binary')\n    parsed = bitTorrentPeerId(buffer)\n  } catch (e) {\n    console.log('peerIdParser.fail', e, str, decodedStr)\n    return UNKNOWN_PEERID_NAME\n  }\n\n  const result = parsed.version\n    ? `${parsed.client} v${parsed.version}`\n    : parsed.client\n  return result\n}\n\nexport const calcProgress = (totalLength, completedLength, decimal = 2) => {\n  const total = parseInt(totalLength, 10)\n  const completed = parseInt(completedLength, 10)\n  if (total === 0 || completed === 0) {\n    return 0\n  }\n  const percentage = completed / total * 100\n  const result = parseFloat(percentage.toFixed(decimal))\n  return result\n}\n\nexport const calcRatio = (totalLength, uploadLength) => {\n  const total = parseInt(totalLength, 10)\n  const upload = parseInt(uploadLength, 10)\n  if (total === 0 || upload === 0) {\n    return 0\n  }\n\n  const percentage = upload / total\n  const result = parseFloat(percentage.toFixed(4))\n  return result\n}\n\nexport const timeRemaining = (totalLength, completedLength, downloadSpeed) => {\n  const remainingLength = totalLength - completedLength\n  return Math.ceil(remainingLength / downloadSpeed)\n}\n\n/**\n * timeFormat\n * @param {int} seconds\n * @param {string} prefix\n * @param {string} suffix\n * @param {object} i18n\n * i18n: {\n *  gt1d: 'More than one day',\n *  hour: 'h',\n *  minute: 'm',\n *  second: 's'\n * }\n */\nexport const timeFormat = (seconds, { prefix = '', suffix = '', i18n }) => {\n  let result = ''\n  let hours = ''\n  let minutes = ''\n  let secs = seconds || 0\n  const i = {\n    gt1d: '> 1 day',\n    hour: 'h',\n    minute: 'm',\n    second: 's',\n    ...i18n\n  }\n\n  if (secs <= 0) {\n    return ''\n  }\n  if (secs > 86400) {\n    return `${prefix} ${i.gt1d} ${suffix}`\n  }\n  if (secs > 3600) {\n    hours = `${Math.floor(secs / 3600)}${i.hour} `\n    secs %= 3600\n  }\n  if (secs > 60) {\n    minutes = `${Math.floor(secs / 60)}${i.minute} `\n    secs %= 60\n  }\n  secs += i.second\n  result = hours + minutes + secs\n  return result ? `${prefix} ${result} ${suffix}` : result\n}\n\nexport const localeDateTimeFormat = (timestamp, locale) => {\n  if (!timestamp) {\n    return ''\n  }\n\n  if (`${timestamp}`.length === 10) {\n    timestamp *= 1000\n  }\n  const date = new Date(timestamp)\n  return date.toLocaleDateString(locale, {\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric',\n    hour: 'numeric',\n    minute: 'numeric',\n    second: 'numeric'\n  })\n}\n\nexport const ellipsis = (str = '', maxLen = 64) => {\n  const len = str.length\n  let result = str\n  if (len < maxLen) {\n    return result\n  }\n\n  if (maxLen > 0) {\n    result = `${result.substring(0, maxLen)}...`\n  }\n\n  return result\n}\n\nexport const getFileSelection = (files = []) => {\n  console.log('getFileSelection===>', files)\n  const selectedFiles = files.filter((file) => file.selected)\n  if (files.length === 0 || selectedFiles.length === 0) {\n    return NONE_SELECTED_FILES\n  }\n\n  if (files.length === selectedFiles.length) {\n    return SELECTED_ALL_FILES\n  }\n\n  const indexArr = []\n  files.forEach((_, index) => {\n    indexArr.push(index)\n  })\n  const result = indexArr.join(',')\n  return result\n}\n\nexport const getTaskName = (task, options = {}) => {\n  const o = {\n    defaultName: '',\n    maxLen: 64, // -1: No limit length\n    ...options\n  }\n  const { defaultName, maxLen } = o\n  let result = defaultName\n  if (!task) {\n    return result\n  }\n\n  const { files, bittorrent } = task\n  const total = files.length\n\n  if (bittorrent && bittorrent.info && bittorrent.info.name) {\n    result = ellipsis(bittorrent.info.name, maxLen)\n  } else if (total === 1) {\n    result = getFileNameFromFile(files[0])\n    result = ellipsis(result, maxLen)\n  }\n\n  return result\n}\n\nexport const getFileNameFromFile = (file) => {\n  if (!file) {\n    return ''\n  }\n\n  let { path } = file\n  if (!path && file.uris && file.uris.length > 0) {\n    path = decodeURI(file.uris[0].uri)\n  }\n\n  const index = path.lastIndexOf('/')\n\n  if (index <= 0 || index === path.length) {\n    return path\n  }\n\n  return path.substring(index + 1)\n}\n\nexport const isMagnetTask = (task) => {\n  const { bittorrent } = task\n  return bittorrent && !bittorrent.info\n}\n\nexport const checkTaskIsSeeder = (task) => {\n  const { bittorrent, seeder } = task\n  return !!bittorrent && seeder === 'true'\n}\n\nexport const getTaskUri = (task, withTracker = false) => {\n  const { files } = task\n  let result = ''\n  if (checkTaskIsBT(task)) {\n    result = buildMagnetLink(task, withTracker)\n    return result\n  }\n\n  if (files && files.length === 1) {\n    const { uris } = files[0]\n    result = uris[0].uri\n  }\n\n  return result\n}\n\nexport const buildMagnetLink = (task, withTracker = false, btTracker = []) => {\n  const { bittorrent, infoHash } = task\n  const { info } = bittorrent\n\n  const params = [\n    `magnet:?xt=urn:btih:${infoHash}`\n  ]\n  if (info && info.name) {\n    params.push(`dn=${encodeURI(info.name)}`)\n  }\n\n  if (withTracker) {\n    const trackers = difference(bittorrent.announceList, btTracker)\n    trackers.forEach((tracker) => {\n      params.push(`tr=${encodeURI(tracker)}`)\n    })\n  }\n\n  const result = params.join('&')\n\n  return result\n}\n\nexport const checkTaskTitleIsEmpty = (task) => {\n  const { files, bittorrent } = task\n  const [file] = files\n  const { path } = file\n  let result = path\n  if (bittorrent && bittorrent.info && bittorrent.info.name) {\n    result = bittorrent.info.name\n  }\n  return result === ''\n}\n\nexport const checkTaskIsBT = (task = {}) => {\n  const { bittorrent } = task\n  return !!bittorrent\n}\n\nexport const isTorrent = (file) => {\n  const { name, type } = file\n  return name.endsWith('.torrent') || type === 'application/x-bittorrent'\n}\n\nexport const getAsBase64 = (file, callback) => {\n  const reader = new FileReader()\n  reader.addEventListener('load', () => {\n    // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL\n    const result = reader.result.split('base64,')[1]\n    callback(result)\n  })\n  reader.readAsDataURL(file)\n}\n\nexport const mergeTaskResult = (response = []) => {\n  let result = []\n  for (const res of response) {\n    result = result.concat(...res)\n  }\n  return result\n}\n\nexport const changeKeysCase = (obj, caseConverter) => {\n  const result = {}\n  if (isEmpty(obj) || !isFunction(caseConverter)) {\n    return result\n  }\n\n  for (const [k, value] of Object.entries(obj)) {\n    const key = caseConverter(k)\n    result[key] = value\n  }\n\n  return result\n}\n\nexport const changeKeysToCamelCase = (obj = {}) => {\n  return changeKeysCase(obj, camelCase)\n}\n\nexport const changeKeysToKebabCase = (obj = {}) => {\n  return changeKeysCase(obj, kebabCase)\n}\n\nexport const validateNumber = (n) => {\n  return !isNaN(parseFloat(n)) && isFinite(n) && Number(n) === n\n}\n\nexport const fixValue = (obj = {}) => {\n  const result = {}\n  for (const [k, v] of Object.entries(obj)) {\n    if (v === 'true') {\n      result[k] = true\n    } else if (v === 'false') {\n      result[k] = false\n    } else if (validateNumber(v)) {\n      result[k] = Number(v)\n    } else {\n      result[k] = v\n    }\n  }\n  return result\n}\n\nexport const separateConfig = (options) => {\n  // user\n  const user = {}\n  // system\n  const system = {}\n  // others\n  const others = {}\n\n  for (const [k, v] of Object.entries(options)) {\n    if (userKeys.indexOf(k) !== -1) {\n      user[k] = v\n    } else if (systemKeys.indexOf(k) !== -1) {\n      system[k] = v\n    } else {\n      others[k] = v\n    }\n  }\n  return {\n    user, system, others\n  }\n}\n\nexport const compactUndefined = (arr = []) => {\n  return arr.filter((item) => {\n    return item !== undefined\n  })\n}\n\nexport const splitTextRows = (text = '') => {\n  text = `${text}`\n  let result = text\n    .replace(/(?:\\\\\\r\\\\\\n|\\\\\\r|\\\\\\n)/g, ' ')\n    .replace(/(?:\\r\\n|\\r|\\n)/g, '\\n')\n    .split('\\n') || []\n  result = result.map((row) => row.trim())\n  return result\n}\n\nexport const convertCommaToLine = (text = '') => {\n  text = `${text}`\n  let arr = text.split(',')\n  arr = arr.map((row) => row.trim())\n  const result = arr.join('\\n').trim()\n  return result\n}\n\nexport const convertLineToComma = (text = '') => {\n  const result = text.trim().replace(/(?:\\r\\n|\\r|\\n)/g, ',')\n  return result\n}\n\nexport const filterVideoFiles = (files = []) => {\n  const suffix = [...VIDEO_SUFFIXES, ...SUB_SUFFIXES]\n  return files.filter((item) => {\n    return suffix.includes(item.extension)\n  })\n}\n\nexport const filterAudioFiles = (files = []) => {\n  return files.filter((item) => {\n    return AUDIO_SUFFIXES.includes(item.extension)\n  })\n}\n\nexport const filterImageFiles = (files = []) => {\n  return files.filter((item) => {\n    return IMAGE_SUFFIXES.includes(item.extension)\n  })\n}\n\nexport const filterDocumentFiles = (files = []) => {\n  return files.filter((item) => {\n    return DOCUMENT_SUFFIXES.includes(item.extension)\n  })\n}\n\nexport const isAudioOrVideo = (uri = '') => {\n  const suffixs = [...AUDIO_SUFFIXES, ...VIDEO_SUFFIXES]\n  const result = suffixs.some((suffix) => {\n    return uri.includes(suffix)\n  })\n  return result\n}\n\nexport const needCheckCopyright = (links = '') => {\n  const uris = splitTaskLinks(links)\n  const avs = uris.filter(uri => {\n    return isAudioOrVideo(uri)\n  })\n\n  const result = avs.length > 0\n  return result\n}\n\nexport const decodeThunderLink = (url = '') => {\n  if (!url.startsWith('thunder://')) {\n    return url\n  }\n\n  let result = url.trim()\n  result = result.split('thunder://')[1]\n  result = Buffer.from(result, 'base64').toString('utf8')\n  result = result.substring(2, result.length - 2)\n  return result\n}\n\nexport const splitTaskLinks = (links = '') => {\n  const temp = compact(splitTextRows(links))\n  const result = temp.map((item) => {\n    return decodeThunderLink(item)\n  })\n  return result\n}\n\nexport const detectResource = (content) => {\n  return RESOURCE_TAGS.some((type) => {\n    return content.includes(type)\n  })\n}\n\nexport const buildFileList = (rawFile) => {\n  rawFile.uid = Date.now()\n  const file = {\n    status: 'ready',\n    name: rawFile.name,\n    size: rawFile.size,\n    percentage: 0,\n    uid: rawFile.uid,\n    raw: rawFile\n  }\n  const fileList = [file]\n  return fileList\n}\n\nexport const isRTL = (locale = 'en-US') => {\n  return SUPPORT_RTL_LOCALES.includes(locale)\n}\n\nexport const getLangDirection = (locale = 'en-US') => {\n  return isRTL(locale) ? 'rtl' : 'ltr'\n}\n\nexport const listTorrentFiles = (files) => {\n  const result = files.map((file, index) => {\n    const extension = getFileExtension(file.path)\n    const item = {\n      // aria2 select-file start index at 1\n      // possible Values: 1-1048576\n      idx: index + 1,\n      extension: `.${extension}`,\n      ...file\n    }\n    return item\n  })\n  return result\n}\n\nexport const getFileName = (fullPath) => {\n  // eslint-disable-next-line\n  return fullPath.replace(/^.*[\\\\\\/]/, '')\n}\n\nexport const getFileExtension = (filename) => {\n  return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2)\n}\n\nexport const removeExtensionDot = (extension = '') => {\n  return extension.replace('.', '')\n}\n\nexport const diffConfig = (current = {}, next = {}) => {\n  const curr = pick(current, Object.keys(next))\n  const result = omitBy(next, (val, key) => {\n    if (isArray(val) || isPlainObject(val)) {\n      return JSON.stringify(curr[key]) === JSON.stringify(val)\n    }\n    return curr[key] === val\n  })\n\n  return result\n}\n\nexport const calcFormLabelWidth = (locale) => {\n  return locale.startsWith('de') ? '28%' : '25%'\n}\n\nexport const parseHeader = (header = '') => {\n  header = header.trim()\n  let result = {}\n  if (!header) {\n    return result\n  }\n\n  const headers = splitTextRows(header)\n  headers.forEach((line) => {\n    const index = line.indexOf(':')\n    const name = line.substring(0, index)\n    const value = line.substring(index + 1).trim()\n    result[name] = value\n  })\n  result = changeKeysToCamelCase(result)\n\n  return result\n}\n\nexport const formatOptionsForEngine = (options = {}) => {\n  const result = {}\n\n  Object.keys(options).forEach((key) => {\n    const kebabCaseKey = kebabCase(key)\n    if (Array.isArray(options[key])) {\n      result[kebabCaseKey] = options[key].join('\\n')\n    } else {\n      result[kebabCaseKey] = `${options[key]}`\n    }\n  })\n\n  return result\n}\n\nexport const buildRpcUrl = (options = {}) => {\n  const { port, secret } = options\n  let result = `${ENGINE_RPC_HOST}:${port}/jsonrpc`\n  if (secret) {\n    result = `token:${secret}@${result}`\n  }\n  result = `http://${result}`\n\n  return result\n}\n\nexport const checkIsNeedRestart = (changed = {}) => {\n  let result = false\n\n  if (isEmpty(changed)) {\n    return result\n  }\n\n  const kebabCaseChanged = changeKeysToKebabCase(changed)\n  needRestartKeys.some((key) => {\n    if (Object.keys(kebabCaseChanged).includes(key)) {\n      result = true\n      return true\n    }\n    return false\n  })\n\n  return result\n}\n\nexport const checkIsNeedRun = (enable, lastTime, interval) => {\n  if (!enable) {\n    return false\n  }\n\n  return (Date.now() - lastTime > interval)\n}\n\nexport const generateRandomInt = (min = 0, max = 10000) => {\n  let result = min\n  const range = max - min\n  result += Math.floor(Math.random() * Math.floor(range))\n  return result\n}\n\nexport const intersection = (array1 = [], array2 = []) => {\n  if (array1.length === 0 || array2.length === 0) {\n    return []\n  }\n\n  return array1.filter(value => array2.includes(value))\n}\n\nexport const cloneArray = (arr = [], reversed = false) => {\n  if (!Array.isArray(arr)) {\n    return arr\n  }\n\n  const result = [...arr]\n  return reversed ? result.reverse() : result\n}\n\nexport const pushItemToFixedLengthArray = (arr = [], maxLength, item) => {\n  const result = arr.length >= maxLength\n    ? [...arr.slice(1, maxLength - 1), item]\n    : [...arr, item]\n  return result\n}\n\nexport const removeArrayItem = (arr = [], item) => {\n  const idx = arr.indexOf(item)\n  if (idx === -1) {\n    return [...arr]\n  }\n\n  const result = [\n    ...arr.slice(0, idx),\n    ...arr.slice(idx + 1)\n  ]\n  return result\n}\n\nexport const getInverseTheme = (theme) => {\n  return (theme === APP_THEME.LIGHT) ? APP_THEME.DARK : APP_THEME.LIGHT\n}\n\nexport const changedConfig = { basic: {}, advanced: {} }\nexport const backupConfig = { theme: undefined, locale: undefined }\n"
  },
  {
    "path": "src/shared/utils/rename.js",
    "content": "const RULE_REGEX = /\\(([^)]*)\\)/\nconst PLUS = '+'\nconst MINUS = '-'\nconst OPERATORS = [PLUS, MINUS]\n\nexport const getRuleString = (out) => {\n  const rule = out.match(RULE_REGEX)\n  const result = rule && rule[1]\n\n  return result\n}\n\nexport const buildRule = (rule) => {\n  let ruleArr\n  let operator = PLUS\n  let init = 1\n  let step = 1\n  let len = 1\n\n  OPERATORS.some(OPT => {\n    if (rule.includes(OPT)) {\n      ruleArr = rule.split(OPT)\n      operator = OPT\n      return true\n    }\n    return false\n  })\n\n  if (ruleArr) {\n    len = ruleArr[0].length\n    init = parseInt(ruleArr[0], 10)\n    step = ruleArr[1] || 1\n    if (operator === MINUS) {\n      step = -step\n    }\n  }\n\n  return {\n    init,\n    step,\n    len\n  }\n}\n\nexport const buildOuts = (uris = [], out = '') => {\n  const result = []\n  const count = uris.length\n  if (count === 0 || !out) {\n    return result\n  }\n\n  if (count === 1) {\n    return [out]\n  }\n\n  const ruleStr = getRuleString(out)\n  if (!ruleStr) {\n    return result\n  }\n  const rule = buildRule(ruleStr)\n\n  let idx\n  let temp\n\n  for (let i = 0; i < count; i++) {\n    idx = `${rule.init + rule.step * i}`.padStart(rule.len, '0')\n\n    temp = out.replace(RULE_REGEX, idx)\n\n    result.push(temp)\n  }\n\n  return result\n}\n"
  },
  {
    "path": "src/shared/utils/tracker.js",
    "content": "import { isEmpty } from 'lodash'\nimport axios from 'axios'\nimport { MAX_BT_TRACKER_LENGTH, ONE_SECOND, PROXY_SCOPES } from '@shared/constants'\n\nexport const convertToAxiosProxy = (proxyServer = '') => {\n  if (!proxyServer) {\n    return\n  }\n\n  const url = new URL(proxyServer)\n  const { username, password, protocol = 'http:', hostname, port } = url\n\n  let result = {\n    protocol: protocol.replace(':', ''),\n    host: hostname,\n    port\n  }\n\n  const auth = username || password\n    ? {\n      username,\n      password\n    }\n    : undefined\n\n  if (auth) {\n    result = {\n      ...result,\n      auth\n    }\n  }\n\n  return result\n}\n\nexport const fetchBtTrackerFromSource = async (source, proxyConfig = {}) => {\n  if (isEmpty(source)) {\n    return []\n  }\n\n  const now = Date.now()\n  const { enable, server, scope = [] } = proxyConfig\n  const proxy = enable && server && scope.includes(PROXY_SCOPES.UPDATE_TRACKERS)\n    ? convertToAxiosProxy(server)\n    : undefined\n\n  // Axios's config.proxy is Node.js only\n  const promises = source.map(async (url) => {\n    return axios.get(`${url}?t=${now}`, {\n      timeout: 30 * ONE_SECOND,\n      proxy\n    }).then((value) => value.data)\n  })\n\n  const results = await Promise.allSettled(promises)\n  const values = results.map((item) => item.value)\n  const result = [...new Set(values)]\n  return result\n}\n\nexport const convertTrackerDataToLine = (arr = []) => {\n  const result = arr.join('\\r\\n').replace(/^\\s*[\\r\\n]/gm, '').trim()\n  return result\n}\n\nexport const convertTrackerDataToComma = (arr = []) => {\n  const result = convertTrackerDataToLine(arr).replace(/(?:\\r\\n|\\r|\\n)/g, ',').trim()\n  return result\n}\n\nexport const reduceTrackerString = (str = '') => {\n  if (str.length <= MAX_BT_TRACKER_LENGTH) {\n    return str\n  }\n\n  const subStr = str.substring(0, MAX_BT_TRACKER_LENGTH)\n  const index = subStr.lastIndexOf(',')\n  if (index === -1) {\n    return subStr\n  }\n\n  const result = subStr.substring(0, index)\n  return result\n}\n"
  },
  {
    "path": "src/shared/utils/tray.js",
    "content": "import { APP_THEME, TRAY_CANVAS_CONFIG } from '@shared/constants'\n\n// Temp Fix: Cannot find module 'lodash'\n// import { bytesToSize } from '@shared/utils'\nconst bytesToSize = (bytes) => {\n  const b = parseInt(bytes, 10)\n  const sizes = ['B', 'KB', 'MB', 'GB', 'TB']\n  if (b === 0) { return '0 KB' }\n  const i = parseInt(Math.floor(Math.log(b) / Math.log(1024)), 10)\n  if (i === 0) { return `${b} ${sizes[i]}` }\n  return `${(b / (1024 ** i)).toFixed(1)} ${sizes[i]}`\n}\n\nconst lightTextColor = '#000'\nconst darkTextColor = '#fff'\nconst baseWidth = TRAY_CANVAS_CONFIG.WIDTH\nconst baseHeight = TRAY_CANVAS_CONFIG.HEIGHT\nconst baseIconWidth = TRAY_CANVAS_CONFIG.ICON_WIDTH\nconst baseIconHeight = TRAY_CANVAS_CONFIG.ICON_HEIGHT\nconst baseTextWidth = TRAY_CANVAS_CONFIG.TEXT_WIDTH\nconst baseFontSize = TRAY_CANVAS_CONFIG.TEXT_FONT_SIZE\nconst fontFamily = 'Arial'\n\nexport const draw = async ({\n  canvas,\n  theme,\n  icon,\n  uploadSpeed,\n  downloadSpeed,\n  scale,\n  resultType\n}) => {\n  if (!canvas) {\n    throw new Error('canvas is required')\n  }\n\n  const width = baseWidth * scale\n  const height = baseHeight * scale\n  const textColor = (theme === APP_THEME.LIGHT) ? lightTextColor : darkTextColor\n  const fontSize = (baseFontSize * scale) + 1\n  const textFont = `${fontSize}px \"${fontFamily}\"`\n  const iconWidth = baseIconWidth * scale\n  const iconHeight = baseIconHeight * scale\n  const textWidth = baseTextWidth * scale\n\n  if (canvas.width !== width) {\n    canvas.width = width\n  }\n\n  if (canvas.height !== height) {\n    canvas.height = height\n  }\n\n  const ctx = canvas.getContext('2d')\n  ctx.clearRect(0, 0, canvas.width, canvas.height)\n\n  if (icon) {\n    ctx.drawImage(icon, 0, 0, iconWidth, iconHeight)\n  }\n\n  ctx.font = textFont\n  ctx.textBaseline = 'top'\n  ctx.textAlign = 'right'\n  ctx.fillStyle = textColor\n\n  const uploadText = `${bytesToSize(uploadSpeed)}/s`\n  const uploadTextY = 0\n  ctx.fillText(uploadText, width, uploadTextY, textWidth)\n\n  const downloadText = `${bytesToSize(downloadSpeed)}/s`\n  const downloadTextY = baseFontSize * scale + 0.5\n  ctx.fillText(downloadText, width, downloadTextY, textWidth)\n\n  const result = transferCanvasTo(canvas, resultType)\n\n  return result\n}\n\nexport const transferCanvasTo = (canvas, type) => {\n  switch (type) {\n  case 'DATA_URL':\n    return canvas.toDataURL()\n  case 'BLOB':\n    return canvas.convertToBlob()\n  case 'BITMAP':\n    return canvas.transferToImageBitmap()\n  default:\n    return canvas.convertToBlob()\n  }\n}\n"
  },
  {
    "path": "static/.gitkeep",
    "content": ""
  }
]